Repository navigation
feat(anki): sync fields, tags, suspension and existence with Anki (#264) - #329
Merged
Merged
Conversation
Step 3 of the sync build order, so the sync page now keeps LWT and Anki in step for everything the export carries, not only reviews. NoteSyncPlanner merges each term and its note three-way against anki_sync_base, what both sides agreed on at the last sync: a field changed on one side is copied to the other, edits to different fields both survive, tags merge as sets, and suspending in Anki ignores the term (and unsuspending restores its status). A field changed differently on both sides, or never agreed, is reported and left alone rather than decided by a clock. New terms in a language with an LWT:: deck are added to Anki; notes added to such a deck become terms. A deletion on one side is only applied on the other once the learner confirms it, and is turned into a re-creation if the other side changed since. AnkiNoteSync runs it as plan, apply to Anki, commit (one transaction), so an interrupted sync converges on the next run. The base has no foreign key to words, by design, and restore or wipe clears it. Also fixes the AnkiConnect client: actions inside multi need their own API version, or they answer in the old format; the push's per-card error check never fired without it. Found by the live test against real Anki, which now covers the note sync too. Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 3, the last piece of #264. The sync page now keeps LWT and Anki in step for everything the export carries, not only reviews. Tested against real Anki in the container from #328.
What syncs
On top of reviews (steps 1 and 2), for every term whose language has an
LWT::{Language}deck in Anki (the export's naming, so no setup):anki_sync_base, the values both sides agreed on at the last sync. A change on one side is copied to the other, and edits to different fields of one term both survive.LWT::deck becomes a term, and itsLwtIdis written back.The two choices that differ from the spike
Both lean towards losing nothing:
WoUpdatedcolumn and no clock comparison between server and desktop.How it runs
The server does the planning and the browser does the writes:
POST /api/v1/anki/notes-planreturns Anki's half.POST /api/v1/anki/notes-commitrecomputes the same plan, applies LWT's half and stores the new base, all in one transaction.An interrupted sync converges on the next run: writes that landed read as already agreed, and a note whose
LwtIdwrite failed still pairs through the base by note id.NoteSyncPlanneris pure: no database, no Anki.AnkiNoteSyncbuilds the inputs throughApkgTermMapper, so values match the export exactly.anki_sync_base(new migration) deliberately has no foreign key towords: a row whose term is gone is how a deletion in LWT reaches Anki. It's scoped per user, note ids are unique per user, and restore and wipe clear it.Bug found by the live test
Actions inside AnkiConnect's
multineed their ownversion. Without it they answer in the old format, and the per-card error check added in #328 never fired. Allmulticalls now sendversion: 6.Tests
NoteSyncPlannerTest(21): every scenario in the spike's acceptance list, plus first-sync conflicts, pairing through the base, orphans, deck scoping and status restore.AnkiNoteSyncIntegrationTest(4, MySQL): an edit in Anki reaching LWT and a quiet second sync, an addition recorded after the browser makes it, a note becoming a term, and a deletion held until confirmed.24-anki-live.cy.ts, new test, against real Anki:composer test:no-coverage(9245),npm test(4440), Psalm (no errors), phpcs, lint,build:allanddocs:buildall pass.21to24pass, andcheck-localesreports 100% for all 11 locales.Docs: the spike now reads "implemented in full", with the two deviations written up. CHANGELOG updated.
🤖 Generated with Claude Code