Skip to content

fix: confirm mismatched note updates - #180

Open
kuator wants to merge 1 commit into
Ajatt-Tools:masterfrom
kuator:fix/confirm-mismatched-recent-notes
Open

kuator wants to merge 1 commit into
Ajatt-Tools:masterfrom
kuator:fix/confirm-mismatched-recent-notes

Conversation

@kuator

@kuator kuator commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Problem

When updating recent Anki notes, mpvacious trusts the configured card count.
If that count is too high, it can overwrite older notes that belong to a
different sentence.

Example

Suppose the card count is set to 3, but you created only one card for the
current subtitle. Two unrelated cards are still inside the ten-minute update
window. Mpvacious currently treats all three as targets and can overwrite the
two older cards.

The same problem can happen with a single card if the most recent note belongs
to a different subtitle.

Change

  • Compare the configured sentence field before updating recent notes.
  • Ignore differences caused only by HTML markup or spacing.
  • If the notes appear unrelated, show a Yes/No prompt using
    mp.input.select.
  • Default to No and cancel if the prompt is dismissed or left unanswered for
    ten seconds.
  • Continue with the update only when the user explicitly chooses Yes.

This keeps intentional updates possible while protecting unrelated notes from
accidental changes.

Tests

Added tests for multi-card mismatches, single-card mismatches, confirmation,
cancellation, timeout handling, and duplicate callbacks.

The full test suite passes with luajit tests/run.lua.

@kuator
kuator force-pushed the fix/confirm-mismatched-recent-notes branch from 4946241 to 4b76c04 Compare August 12, 2026 07:16
@kuator

kuator commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

A few times I updated unrelated note because I forgot, so I added handling a single-mismatched card as well

Comment thread mpvacious/anki/note_exporter.lua Outdated
Comment on lines +706 to +809
local function test_update_last_note_confirms_unrelated_notes()
local now_ms = os.time() * 1000
local note_ids = { now_ms - 5, now_ms - 4, now_ms - 3, now_ms - 2, now_ms - 1 }
local sentences = { "unrelated one", "unrelated two", "target", "<b>target</b>", "target" }
local updated = false
local notification = nil
local original_notify = h.notify
local original_add_key_binding = mp.add_forced_key_binding
local original_remove_key_binding = mp.remove_key_binding
local original_create_osd_overlay = mp.create_osd_overlay
local confirmation_bindings = {}
local confirmation_overlay = { update = function() return end, remove = function() return end }
local cleared_state = 0
local refreshed_menu = 0
h.notify = function(message)
notification = message
end
mp.add_forced_key_binding = function(key, _, fn)
confirmation_bindings[key] = fn
end
mp.remove_key_binding = function()
return
end
mp.create_osd_overlay = function()
return confirmation_overlay
end

local test_exporter = make_exporter().init(
{
get_last_note_ids = function()
return note_ids
end,
get_note_fields = function(note_id)
for i, id in ipairs(note_ids) do
if id == note_id then
return { SentKanji = sentences[i] }
end
end
end,
},
{
get_cards = function() return 5 end,
clear_options = function() cleared_state = cleared_state + 1 end,
},
{
clear = function() cleared_state = cleared_state + 1 end,
menu = { update = function() refreshed_menu = refreshed_menu + 1 end },
},
nil,
nil,
{
fail_if_not_ready = function() return end,
config = function()
return {
sentence_field = "SentKanji",
reload_config_before_card_creation = false,
}
end,
}
)
test_exporter.update_notes = function()
updated = true
end

test_exporter.update_last_note(false)
h.assert_equals(updated, false)
h.assert_equals(h.is_substr(confirmation_overlay.data, "Only the newest 3 of 5 notes share SentKanji."), true)
h.assert_equals(h.is_substr(confirmation_overlay.data, "Update all 5 anyway?"), true)
h.assert_equals(type(confirmation_bindings.y), "function")
h.assert_equals(type(confirmation_bindings.n), "function")
h.assert_equals(type(confirmation_bindings.ENTER), "function")
h.assert_equals(type(confirmation_bindings.left), "function")
h.assert_equals(type(confirmation_bindings.right), "function")
h.assert_equals(h.is_substr(confirmation_overlay.data, OSD:new():blue("[n] No"):get_text()), true)

confirmation_bindings.left()
h.assert_equals(h.is_substr(confirmation_overlay.data, OSD:new():blue("[y] Yes"):get_text()), true)
confirmation_bindings.right()
h.assert_equals(h.is_substr(confirmation_overlay.data, OSD:new():blue("[n] No"):get_text()), true)
confirmation_bindings.left()
confirmation_bindings.ENTER()
h.assert_equals(updated, true)

updated = false
test_exporter.update_last_note(false)
confirmation_bindings.y()
h.assert_equals(updated, true)

updated = false
test_exporter.update_last_note(false)
confirmation_bindings.n()
h.assert_equals(updated, false)
h.assert_equals(notification, "Card update cancelled.")
h.assert_equals(cleared_state, 2)
h.assert_equals(refreshed_menu, 1)

sentences[1], sentences[2] = "target", "target"
test_exporter.update_last_note(false)
h.assert_equals(updated, true)
h.notify = original_notify
mp.add_forced_key_binding = original_add_key_binding
mp.remove_key_binding = original_remove_key_binding
mp.create_osd_overlay = original_create_osd_overlay
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is longer than 21 lines. Maybe it could be split into smaller logical chunks. The same applies to test_update_notes_confirms_unrelated_single_note.

@tatsumoto-ren

tatsumoto-ren commented Aug 12, 2026

Copy link
Copy Markdown
Member

Have you tried mpv's mp.input.select? It should simplify the logic significantly.

https://github.com/mpv-player/mpv/blob/f4d13e1c2c91f3a56e589aef9cb44cbc02e26e47/DOCS/man/select.rst

local input = require('mp.input')

local function confirm_yes_no(prompt, on_result)
    local resolved = false

    local function finish(result)
        if resolved then return end  -- guard against double-resolve (submit+closed)
        resolved = true
        on_result(result)
    end

    input.select {
        prompt = prompt,
        items = { "Yes", "No" },
        default_item = 1,           -- [1] "Yes" highlighted by default
        -- submit(idx): 1-based index into `items`.
        submit = function(idx)
            finish(idx == 1)
        end,
        -- closed: fired on dismiss (e.g. ESC or when the prompt is superseded).
        -- Treated as neither Yes nor No.
        closed = function()
            finish(nil)
        end,
    }
end

Usage:

confirm_yes_no("Update this note anyway?", function(confirmed)
    if confirmed == true then
        proceed_with_update()
    else
        clear_update_state()
        h.notify("Card update cancelled.", "info", 2)
    end
end)

@kuator

kuator commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Sorry, I've been busy late, will try to addres your comments when I have more time

@kuator
kuator force-pushed the fix/confirm-mismatched-recent-notes branch 2 times, most recently from 26b21f3 to 988311a Compare August 16, 2026 16:19
@kuator
kuator force-pushed the fix/confirm-mismatched-recent-notes branch from 988311a to 7e2c0a2 Compare September 2, 2026 11:32
@kuator

kuator commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Rebased this onto the current master and replaced the custom confirmation UI with mp.input.select as suggested. The branch is now a single conflict-free commit, and luajit tests/run.lua passes.

@kuator kuator changed the title fix: confirm mismatched multi-card updates fix: confirm mismatched note updates Sep 2, 2026
@kuator
kuator force-pushed the fix/confirm-mismatched-recent-notes branch 2 times, most recently from 6677556 to fc6349b Compare September 5, 2026 06:19
@kuator

kuator commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

bump

@kuator
kuator force-pushed the fix/confirm-mismatched-recent-notes branch from fc6349b to 1854e7d Compare September 6, 2026 08:22
@kuator

kuator commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

bump @tatsumoto-ren

@tatsumoto-ren

Copy link
Copy Markdown
Member

I'll try to refactor this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants