Fixes#4936
## Problem
The `check-linked-issue` workflow runs on every `opened` and `edited` PR
event. When a PR had no linked issue, each edit triggered a new bot
comment, resulting in duplicates (e.g. #4934).
## Solution
Before posting the comment, list the existing PR comments and skip
posting if the bot has already left one with the same message.
The `missing-issue` label re-application is harmless since GitHub
deduplicates labels automatically.
<!--
Title (for the Pull Request title field at the top):
Use a short prefix so the change type is obvious. You do not need to
repeat it in the body below.
Examples:
- fix: — bugfix
- feat: — feature
- refactor: — internal change without user-facing feature
- docs: — documentation only
- chore: — tooling, CI, deps, build housekeeping
- test: — tests only
-->
## Linked issue (required)
<!-- Fixes#123 / Closes#123 / Refs #123 -->
closes#4722
## Summary / motivation (required)
<!-- What this PR does and why. For larger changes, add enough context
for reviewers. -->
I used this nice script I found:
https://gist.github.com/onnimonni/3462f958c7d235417863651974514525
For the reasons behind this change see:
- #4722
## Steps to reproduce (required, use N/A if not applicable)
<!-- Steps to reproduce: how to trigger the bug in the broken state (the
"before").
- Mainly for bugfixes;
- For bugs: numbered steps before the fix. For non-bugs: write N/A.
- use N/A for features, refactors, docs, chore, etc.
-->
(N/A)
## How to test (required)
<!--- How to test: how you verified the change (checks, unit tests,
manual steps, edge cases — the "after" or general validation). --->
See it run in my repo here:
https://github.com/Luc-Mcgrady/anki/actions/runs/26718877866
### Checklist (minimum)
- [X] I ran `./ninja check` or an equivalent relevant check locally.
- [ ] I added or updated tests when the change is non-trivial or
behavior changed.
### Details
<!-- Commands, manual steps, edge cases, and what you observed -->
## Before / after behavior (optional)
<!-- For bugfixes: behavior before vs after. For other types: N/A or a
short note. -->
## Risk / compatibility / migration (optional)
<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->
## UI evidence (required for visual changes; otherwise N/A)
<!-- Screenshot or short video -->
## Scope
- [X] This PR is focused on one change (no unrelated edits).
## Linked issue
Closes#4816
## Summary / motivation
Adds two workflows to enforce the rule that every PR must be linked to
an existing issue:
- **check-linked-issue**: triggers on PR open/edit, applies the
`missing-issue` label and notifies the author if no linked issue is
found. Removes the label if the author later links one.
- **auto-close-missing-issue**: runs daily and closes any PR that has
had the `missing-issue` label for more than 4 days.
Hotfixes (title contains `hotfix`) and Dependabot PRs are exempt.
## How to test
1. Open a PR without a linked issue, the `missing-issue` label should be
applied and a comment posted.
2. Edit the PR description to add `Closes #<number>`, the label should
be removed.
3. Trigger the auto-close workflow manually via Actions → `Auto-close
PRs without linked issue` → Run workflow, and verify it closes PRs that
have had the label for over 4 days.
## Linked issue
Closes#4874
## Summary / motivation
Adds `tools/coverage/check-coverage-regression.py` to compare line
coverage percentages from the current PR against the baseline saved from
main (introduced in #4875). If any stack regresses beyond the
configured tolerance (0.10%), the CI fails with a clear message showing
the delta.
Stacks checked: Rust, python-pylib, python-qt, TypeScript.
## How to test
Try to add some new code without any tests. The Ci must fail.
## Before / after behavior
Before: no signal when a PR reduces coverage below the current main
level.
After: CI fails on `Check coverage regression` with output like:
```
[rust] REGRESSION: 62.64% -> 61.00% (delta: -1.64%, tolerance: 0.10%)
1 stack(s) with coverage regression: rust
```
## Linked issue
Closes#4873
## Summary
Build and package the fcitx5-qt6 plugin.
Latest release CI run:
https://github.com/ankitects/anki/actions/runs/26294296416
## How to test
1. Run installer build on Linux: `./ninja installer:build`.
2. Go to the Qt build directory
(`out/installer/build/anki/linux/zip/anki/app_packages/PyQt6/Qt6`) and
confirm you see the following files:
1.
`plugins/platforminputcontexts/libfcitx5platforminputcontextplugin.so`
2. `plugins/dbusaddons/libFcitx5Qt6DBusAddons.so*`
## Linked issue
Refs #4874
## Summary / motivation
Stores the coverage results from every push to `main` in a GitHub
Actions
cache (`coverage-baseline-linux-{sha}`). This is the foundation for a
follow-up PR that will compare PR coverage against this baseline and
fail
if any stack regresses.
No behavior change for PRs yet — the baseline is only saved, not used.
## Before / after behavior
Before: no coverage data persisted between CI runs.
After: each push to `main` saves `out/coverage/` as a cache entry, keyed
by commit SHA, retrievable by prefix `coverage-baseline-linux-`.
## Linked issue
Closes#4863
## Summary / motivation
Adds Playwright as the e2e test framework so contributors can write
browser-based tests against a real headless Anki instance. There was no
automated way to exercise mediasrv pages, SvelteKit routes, or the
`/_anki/` RPC surface from a browser, this PR establishes that harness.
Key pieces:
- `qt/tests/launch_anki_for_e2e.py` — spawns a throwaway Anki instance
(temp `ANKI_BASE`, `QT_QPA_PLATFORM=offscreen`). Pre-seeds `prefs21.db`
so Anki skips the language picker and profile chooser and goes straight
to serving mediasrv.
- `playwright.config.ts` — points `webServer` at the launcher; polls
`/favicon.ico` as the readiness probe.
- `ts/tests/e2e/` — `fixtures.ts` base and a sanity spec that verifies
mediasrv is reachable and a SvelteKit page hydrates.
- `justfile` — `just test-e2e` recipe; Chromium installed to
`out/playwright-browsers/`.
- CI — e2e step in `check-linux`; failed-run artifacts uploaded for 7
days.
- `docs/e2e-testing.md` — contributor guide covering setup, managed vs
reuse-server modes, and writing new tests.
## How to test
Build the project once, then run the e2e suite in managed mode (no
separate `./run` needed — the launcher is started automatically):
```shell
just build
just test-e2e
```
## Before / after behavior (optional)
Before: no browser-level test harness existed.
After: `just test-e2e` drives a real headless Anki instance via
Playwright.
## Risk / compatibility / migration
No production code changed. New dev-only files and CI step only.
Chromium is installed to `out/playwright-browsers/` (gitignored) and
does not affect the regular build.
---------
Co-authored-by: Abdo <abdo@abdnh.net>
## Linked issue
Closes#4859
## Summary
Add tests for the build_installer.py script with 100% coverage.
## How to test
Run `just test-py --coverage --html` and browse coverage data.
## Linked issue
Closes#4838
## Summary/motivation
Adds `coverage.py`-based test coverage for both Python test suites
(`pylib` and `qt`). Introduces `just test-py --coverage` and `just
test-py --coverage --html`, plus the `just test --coverage`.
Coverage reports are written to `out/coverage/`.
## How to test
```sh
# Existing behavior unchanged
just test-py
# Terminal summary + enforces thresholds
just test-py --coverage
# Terminal summary + HTML reports under out/coverage/
just test-py --coverage --html
# Umbrella (Python only for now)
just test --coverage
just test --coverage --html
```
### Checklist (minimum)
- [x] I ran `./ninja check` or an equivalent relevant check locally.
### Details
- `coverage` dependency pinned to >=7.13.5 in pyproject.toml.
- The `coverage` umbrella recipe currently delegates to Python only for
now
## Before / after behavior
Before: no `just test-py`, no coverage support.
After: `just test-py` runs Python tests via ninja; `just test-py
--coverage`
runs them with `coverage.py` and enforces minimum line coverage.
---------
Co-authored-by: Abdo <abdo@abdnh.net>
Pass the `--notes-start-tag` argument to `gh release` with the latest
release tag. Without this, the 26.05b1 release was including notes for
the 25.09.2 release for some reason.
## Linked issue
#4678
## Summary
This enables native Windows ARM64 builds for Briefcase. Depends on #4797
## How to test
- Run `./tools/ninja installer` in a Windows ARM64 machine.
- Check the architecture of the installer under `./out/installer/dist`
by going to Properties > Compatibility and confirming emulation settings
are disabled.
- Install the package and confirm Anki.exe is a native binary.
- Open Anki, go to the [debug
console](https://docs.ankiweb.net/misc.html#debug-console) and run the
following code to check the architecture of the Python build:
```python
import platform
print(platform.machine(), platform.python_compiler())
```
Closes#4793
- Add `workflow_dispatch` trigger to CI (with macOS/Windows support)
- Allow prepare-release and release workflows from any branch
- Add `skip-ci-check` input for hotfix releases
- Add `just release::prepare` and `just ci` recipes
- Make `qt/release/build.sh` find uv in CI and local builds
- Change publish-testpypi environment from testpypi to release
- Add anki-release wheel build step
---------
Co-authored-by: Andrew Sanchez <andrewsanchez@users.noreply.github.com>
Co-authored-by: Fernando Lins <1887601+fernandolins@users.noreply.github.com>
migrates Anki Desktop packaging from the legacy
NSIS/uv-based installer to [BeeWare
Briefcase](https://briefcase.readthedocs.io/). This branch integrates
work from many related issues and PRs to deliver cross-platform native
installers (MSI on Windows, .app on macOS, PyInstaller on Linux) with
code signing, notarization, and file association support.
## Integrated PRs
- #4585 — Set up Briefcase
- #4596 — Add Briefcase icons
- #4598 — Handle Briefcase file associations
- #4601 — Add Briefcase app permissions
- #4609 — Customize Briefcase's MSI installer
- #4616 — Set up Briefcase code signing and notarization
- #4618 — Fix Briefcase packaging for x86 Macs
- #4623 — Customize Briefcase's Linux template
- #4627 — List required Debian packages for Briefcase installer
- #4630 — Update Briefcase's Windows template
- #4631 — Rewrite Linux install/uninstall scripts for PyInstaller
- #4638 — Use PyInstaller on Linux
- #4645 — Update installer docs
- #4654 — Disable Briefcase's universal builds for macOS
- #4672 — Deal with existing NSIS installations in MSI installer
- #4676 — Remove duplicate Briefcase icons
- #4677 — Tweak Linux scripts for new installer
- #4709 — Add anki-console.bat to Briefcase's Windows package
## Related Issues
- #4557 — Evaluate BeeWare Briefcase for Anki packaging and distribution
- #4678 — Support native Windows ARM64 builds for Briefcase
- #4688 — Linux installer: migrate to PyInstaller and rewrite install
scripts
- #4689 — Investigate startup performance with Briefcase
- #4690 — Specify required Linux system packages for Briefcase
- #4691 — Investigate Windows ARM64 support with Briefcase
- #4692 — Test on Linux ARM with Briefcase
- #4693 — Separate ARM and Intel macOS releases
- #4694 — Update developer documentation for Briefcase installer
- #4695 — Support upgrade/downgrade with the Briefcase installer
- #4696 — Update user documentation for new installer
- #4702 — Update Briefcase's Windows template with upstream security fix
and OS version check
- #4703 — Follow-up tweaks to Linux install/uninstall scripts
## Related PRs
- #4619 — Enable Windows ARM64 support
- #4632 — Release action
---------
Co-authored-by: Abdo <abdo@abdnh.net>
Co-authored-by: Andrew Sanchez <andrewsanchez@users.noreply.github.com>
Co-authored-by: Fernando Lins <1887601+fernandolins@users.noreply.github.com>
## Changes
Revert changes to the CONTRIBUTORS file check introduced in
https://github.com/ankitects/anki/pull/4593#discussion_r2908422240
The main problem with checking the actual emails in the file instead of
the git log is that the check will inevitably fail when the PR author
occasionally makes a commit using the GitHub UI. The solution for this
used to be to also make a change to the file using the GitHub UI. This
stopped working after the recent change, except if the author lists
multiple emails.