From 390cf03e8d5be812eed613b9630bdd38d154be4a Mon Sep 17 00:00:00 2001 From: sudacode Date: Tue, 27 Oct 2026 22:16:24 -0700 Subject: [PATCH] fix(plugins): report merged card-mined ids as the kept note - Resolve note ids merged away by field grouping to the surviving note before emitting card-mined, deduplicating results - Update plugin docs and setCardsReadyCallback comment to match --- docs-site/plugins.md | 2 +- src/anki-integration.test.ts | 12 ++++++++++++ src/anki-integration.ts | 6 ++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs-site/plugins.md b/docs-site/plugins.md index e4df56bd..2b9a1ac8 100644 --- a/docs-site/plugins.md +++ b/docs-site/plugins.md @@ -130,6 +130,6 @@ A panel's `signal` aborts when the user closes it or another panel replaces it. mpv events: `connection-change`, `subtitle-change`, `subtitle-timing`, `time-pos-change`, `duration-change`, `pause-change`, `fullscreen-change`, `media-path-change`, `media-title-change`, `subtitle-track-change`, `subtitle-track-list-change`, `secondary-subtitle-change`, `secondary-subtitle-track-change`, `secondary-subtitle-delay-change`, `secondary-subtitle-visibility`, `client-message`, and `end-file`. `end-file` gives `{ reason, playlistEntryId, error }`, where `reason` is `eof`, `stop`, `quit`, `error`, `redirect`, or `unknown`. -Anki events: `card-mined` gives `{ noteIds }` once SubMiner has finished filling in a mined card (sentence, audio, image, and field grouping). For YouTube cards whose media waits on the download, it fires once that media is added or the download fails. With `ankiConnect.behavior.autoUpdateNewCards` off, it fires as soon as SubMiner sees the card. If field grouping merged the card into an older one, the id may no longer exist. +Anki events: `card-mined` gives `{ noteIds }` once SubMiner has finished filling in a mined card (sentence, audio, image, and field grouping). For YouTube cards whose media waits on the download, it fires once that media is added or the download fails. With `ankiConnect.behavior.autoUpdateNewCards` off, it fires as soon as SubMiner sees the card. If field grouping merged the card into an older one, you get the older card's id. `anki.invoke` takes `{ timeoutMs }` (default 10 seconds), rejects on an Anki error, and never retries, so an action like `sync` or `addNote` runs at most once. diff --git a/src/anki-integration.test.ts b/src/anki-integration.test.ts index 8d3346f0..90665a1a 100644 --- a/src/anki-integration.test.ts +++ b/src/anki-integration.test.ts @@ -274,6 +274,18 @@ test('AnkiIntegration resolves merged-away note ids to the kept note id', () => assert.equal(integration.resolveCurrentNoteId(444), 444); }); +test('AnkiIntegration reports ready cards under the note that kept them after a merge', () => { + const integration = createIntegration({}); + const ready: number[][] = []; + integration.setCardsReadyCallback((noteIds) => ready.push(noteIds)); + const internals = internalsOf(integration); + internals.rememberMergedNoteIds(111, 222); + + internals.reportCardsReady([111, 222, 444]); + + assert.deepEqual(ready, [[222, 444]]); +}); + // --- Sentence highlighting -------------------------------------------------- const MPV_SENTENCE = '先日 貴様らが潜入した キールダンジョンから―'; diff --git a/src/anki-integration.ts b/src/anki-integration.ts index 960db6d5..fcf6c65a 100644 --- a/src/anki-integration.ts +++ b/src/anki-integration.ts @@ -352,8 +352,10 @@ export class AnkiIntegration { private emitCardsReady(noteIds: number[]): void { if (noteIds.length === 0) return; + // Field grouping may have merged a note away; report the note that kept its content. + const currentNoteIds = [...new Set(noteIds.map((noteId) => this.resolveCurrentNoteId(noteId)))]; try { - this.cardsReadyCallback?.(noteIds); + this.cardsReadyCallback?.(currentNoteIds); } catch (error) { log.warn('cardsReady callback failed:', (error as Error).message); } @@ -1821,7 +1823,7 @@ export class AnkiIntegration { /** * Called once SubMiner has finished writing a mined card (fields, media including queued YouTube * media, and field grouping), or as soon as polling or the proxy sees it when auto-update is off. - * Field grouping may have merged the note away by then. + * Ids merged away by field grouping are reported as the note that kept them. */ setCardsReadyCallback(callback: ((noteIds: number[]) => void) | null): void { this.cardsReadyCallback = callback;