The message "An error occurred when generating an image occlusion note" is sometimes shown when clicking the check button in the Image Occlusion editor
Checked for duplicates?
- [x] This issue is not a duplicate
Does it also happen in the desktop version?
- [ ] This bug does not occur in the latest version of Anki Desktop
What are the steps to reproduce this bug?
In the first video below, I added an occlusion to an existing Image Occlusion note. When pressing the "Save" button, the message "2 cards updated" was shown. This is expected behavior.
In the second video below, I opened the Image Occlusion editor and didn't make any changes. I repeated this multiple times to show that sometimes the message "2 cards updated" was shown and sometimes the message "An error occurred when generating an image occlusion note".
- Time number 1: "2 cards updated"
- Time number 2: "2 cards updated"
- Time number 3: "An error occurred when generating an image occlusion note"
- Time number 4: "2 cards updated"
- Time number 5: "2 cards updated"
- Time number 6: "An error occurred when generating an image occlusion note"
- Time number 7: "An error occurred when generating an image occlusion note"
- Time number 8: "An error occurred when generating an image occlusion note"
- Time number 9: "An error occurred when generating an image occlusion note"
https://github.com/user-attachments/assets/08c3e219-e0f4-496f-974f-9733004896c0
https://github.com/user-attachments/assets/df3182a3-4a01-4705-ae3b-3a47d3ebc756
I only got the message "An error occurred when generating an image occlusion note" when I didn't make changes in the Image Occlusion editor. That is, I didn't get that message after having made some changes in the Image Occlusion editor, so I can't confirm that it actually means that the changes that were done in the Image Occlusion editor were not saved.
Even if the changes are saved when that message is shown, I believe that it shouldn't be shown in the first place because it might lead to some users think that the changes that they did in the Image Occlusion editor were not saved, which could ultimately cause her/him to waste some time going through their notes to confirm that the changes were actually saved.
Expected behaviour
The message "An error occurred when generating an image occlusion note" shouldn't be shown when no changes were done in the Image Occlusion editor.
Debug info
AnkiDroid Version = 2.23.0beta4 (367917ada802088c982a8ce11312fbb35d4290c3)
Backend Version = 0.1.62-anki25.09.2 (25.09.2 3890e12c9e48c028c3f12aa58cb64bd9f8895e30)
Android Version = 15 (SDK 35)
ProductFlavor = full
Device Info = samsung | samsung | gta4xlswifi | gta4xlswifixx | SM-P620 | s5e8825
Webview User Agent = Mozilla/5.0 (Linux; Android 15; SM-P620 Build/AP3A.240905.015.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.102 Safari/537.36
ACRA UUID = a32527bb-1a56-4c2f-92a8-65ce53ea03ff
FSRS = 5.1.0 (Enabled: false)
Crash Reports Enabled = true
(Optional) Anything else you want to share?
No response
Research
- [x] I have checked the manual and the FAQ and could not find a solution to my issue
- [ ] (Optional) I have confirmed the issue is not resolved in the latest alpha release (instructions)
this is most likely related to backend
Relevant code (permalinks - may not be up to date):
Frontend logic
https://github.com/ankitects/anki/blob/2d4de33cf3160342c4c704c294e643c3e11071b1/ts/routes/image-occlusion/add-or-update-note.svelte.ts#L19-L52
proto
https://github.com/ankitects/anki/blob/2d4de33cf3160342c4c704c294e643c3e11071b1/proto/anki/image_occlusion.proto#L26-L27
service
https://github.com/ankitects/anki/blob/2d4de33cf3160342c4c704c294e643c3e11071b1/rslib/src/image_occlusion/service.rs#L38-L50
impl
https://github.com/ankitects/anki/blob/2d4de33cf3160342c4c704c294e643c3e11071b1/rslib/src/image_occlusion/imagedata.rs#L134-L155
There are two bugs:
- Add an occlusion, then edit the note, making no changes
- The mask may be moved
add = "{{c1::image-occlusion:rect:left=.3734:top=.2508:width=.3858:height=.2429:oi=1}}<br>"
update = "{{c1::image-occlusion:rect:left=.3736:top=.2504:width=.3861:height=.2425:oi=1}}<br>"
BackendInvalidInputExceptionis thrown fromupdateImageOcclusionNoteif the input is identical to the current note data
Test
Subject: [PATCH]
---
Index: AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt (revision 98a5a5a899b3a62bb136de8af9271d40c5241f5f)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt (date 1765052484554)
@@ -148,6 +148,8 @@
): ByteArray {
val methodName = uri.backendMethodName ?: throw IllegalArgumentException("unhandled request: $uri")
+ Timber.wtf("POST REQUEST: %s", methodName)
+
val resolvedUiMethod =
when (val uiResponse = activity.handleUiPostRequest(methodName, bytes)) {
is UiPostRequestResponse.Handled -> return uiResponse.data
Index: AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt (revision 98a5a5a899b3a62bb136de8af9271d40c5241f5f)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt (date 1765052760738)
@@ -21,7 +21,10 @@
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
+import anki.backend.BackendError
import anki.collection.OpChanges
+import anki.image_occlusion.AddImageOcclusionNoteRequest
+import anki.image_occlusion.UpdateImageOcclusionNoteRequest
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.NoteEditorFragment
@@ -50,6 +53,7 @@
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
+import net.ankiweb.rsdroid.BackendException
import timber.log.Timber
interface PostRequestHandler {
@@ -154,12 +158,23 @@
"importAnkiPackage" to { bytes -> lifecycleScope.async { importAnkiPackageUndoable(bytes) } },
"addImageOcclusionNote" to { bytes ->
lifecycleScope.async {
+ val data = AddImageOcclusionNoteRequest.parseFrom(bytes)
+ Timber.wtf("%s", data.toString())
withCol { addImageOcclusionNoteRaw(bytes) }
}
},
"updateImageOcclusionNote" to { bytes ->
lifecycleScope.async {
- withCol { updateImageOcclusionNoteRaw(bytes) }
+ val request = UpdateImageOcclusionNoteRequest.parseFrom(bytes)
+ Timber.wtf("%s", request.toString())
+ withCol { updateImageOcclusionNoteRaw(bytes) }.also {
+ try {
+ val ex = BackendException.fromError(BackendError.parseFrom(it))
+ Timber.wtf(ex)
+ } catch (e: Exception) {
+
+ }
+ }
}
},
"deckOptionsReady" to { bytes -> lifecycleScope.async { deckOptionsReady(bytes) } },
Index: AnkiDroid/src/test/java/com/ichi2/AA.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/AA.kt b/AnkiDroid/src/test/java/com/ichi2/AA.kt
new file mode 100644
--- /dev/null (date 1765053409563)
+++ b/AnkiDroid/src/test/java/com/ichi2/AA.kt (date 1765053409563)
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2025 David Allison <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.ichi2
+
+import android.annotation.SuppressLint
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import anki.backend.BackendError
+import anki.image_occlusion.addImageOcclusionNoteRequest
+import anki.image_occlusion.updateImageOcclusionNoteRequest
+import com.ichi2.anki.RobolectricTest
+import net.ankiweb.rsdroid.BackendException
+import net.ankiweb.rsdroid.exceptions.BackendInvalidInputException
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AA : RobolectricTest() {
+ override fun getCollectionStorageMode(): CollectionStorageMode {
+ return CollectionStorageMode.ON_DISK
+ }
+
+ @SuppressLint("CheckResult")
+ @Test
+ fun `image occlusion update`() {
+ val ntid = col.notetypes.imageOcclusion.id
+
+ val addInput = "{{c1::image-occlusion:rect:left=.3734:top=.2508:width=.3858:height=.2429:oi=1}}<br>"
+ val updateInput = "{{c1::image-occlusion:rect:left=.3736:top=.2504:width=.3861:height=.2425:oi=1}}<br>"
+
+ val addRequest = addImageOcclusionNoteRequest {
+ this.notetypeId = ntid
+ this.imagePath = "/Users/davidallison/Documents/Screenshots/Screenshot 2025-12-06 at 20.37.46.png"
+ this.occlusions = addInput
+ }.toByteArray()
+
+ col.addImageOcclusionNoteRaw(addRequest)
+
+ assertThat(col.cardCount(), equalTo(1))
+
+ val cid = col.findCards("").single()
+ val nid = col.getCard(cid).nid
+
+ val updateRequest = updateImageOcclusionNoteRequest {
+ this.noteId = nid
+ this.occlusions = updateInput
+ }.toByteArray()
+
+ col.updateImageOcclusionNoteRaw(updateRequest).let { res ->
+ val ex = BackendException.fromError(BackendError.parseFrom(res))
+ if (ex is BackendInvalidInputException) {
+ Assert.fail("1")
+ }
+ }
+
+ col.updateImageOcclusionNoteRaw(updateRequest).let { res ->
+ val ex = BackendException.fromError(BackendError.parseFrom(res))
+ if (ex is BackendInvalidInputException) {
+ Assert.fail("2")
+ }
+ }
+
+ }
+}
\ No newline at end of file
Index: libanki/testutils/src/main/java/com/ichi2/anki/libanki/testutils/AnkiTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/libanki/testutils/src/main/java/com/ichi2/anki/libanki/testutils/AnkiTest.kt b/libanki/testutils/src/main/java/com/ichi2/anki/libanki/testutils/AnkiTest.kt
--- a/libanki/testutils/src/main/java/com/ichi2/anki/libanki/testutils/AnkiTest.kt (revision 98a5a5a899b3a62bb136de8af9271d40c5241f5f)
+++ b/libanki/testutils/src/main/java/com/ichi2/anki/libanki/testutils/AnkiTest.kt (date 1765049986304)
@@ -344,4 +344,7 @@
val Notetypes.cloze
get() = byName("Cloze")!!
+
+ val Notetypes.imageOcclusion
+ get() = byName("Image Occlusion")!!
}
@iamllama FYI are these known bugs? Both seem unintentional from reading the code
We can mark this as 'upstream issue' after a quick discussion, and the 2 image occlusion bugs are reported & linked
I'd need to look into (1) a bit more, but was able to reproduce (2) (the error toast, not the invalid input exception 👀) and will open a pr shortly for it. I don't think either have already been reported as issues
Just to note: there's a .patch in my comment (Test) which reproduces the issue on AnkiDroid. Assert.fail("2") is triggered