diff --git a/changes/fix-anki-configured-word-fields.md b/changes/fix-anki-configured-word-fields.md
new file mode 100644
index 00000000..a982ccc5
--- /dev/null
+++ b/changes/fix-anki-configured-word-fields.md
@@ -0,0 +1,4 @@
+type: fixed
+area: anki
+
+- Word-card enrichment now always writes sentence text and audio to the configured AnkiConnect fields, while dedicated Lapis/Kiku sentence-card and audio-card actions keep their compatible field names.
diff --git a/docs-site/anki-integration.md b/docs-site/anki-integration.md
index d64c8c02..59b39946 100644
--- a/docs-site/anki-integration.md
+++ b/docs-site/anki-integration.md
@@ -136,6 +136,8 @@ SubMiner maps its data to your Anki note fields. Configure these under `ankiConn
Field names are matched against your Anki note type case-insensitively (an exact match wins, then a lowercase comparison). If a configured field does not exist on the note type, SubMiner skips it without error.
+These mappings always control normal word-card enrichment, including Yomitan proxy/polling updates and manual clipboard updates. Enabling Lapis or Kiku does not replace the configured word-card sentence and audio fields with `Sentence` and `SentenceAudio`. The dedicated sentence-card and audio-card shortcuts still use those Lapis/Kiku field names.
+
Two related options live alongside `fields`: `ankiConnect.deck` (target deck; empty falls back as described above) and `ankiConnect.tags` (tags added to mined cards, default `["SubMiner"]`; set `[]` to disable tagging). The `miscInfo` content is controlled by `ankiConnect.metadata.pattern` (default `[SubMiner] %f (%t)`; tokens: `%f` filename, `%F` filename with extension, `%t` timestamp, `%T` timestamp with milliseconds, `
` newline).
### Minimal Config
@@ -233,7 +235,7 @@ Animated AVIF requires an AV1 encoder (`libaom-av1`, `libsvtav1`, or `librav1e`)
When media is available, mined-card overlay and system notifications include the same current-frame thumbnail.
-`overwriteAudio` applies to automatic card updates and duplicate-card enrichment. Manual clipboard subtitle updates (`Ctrl/Cmd+C`, then `Ctrl/Cmd+V`) always replace generated sentence audio, while leaving the word audio field unchanged.
+`overwriteAudio` applies to automatic card updates and duplicate-card enrichment. Manual clipboard subtitle updates (`Ctrl/Cmd+C`, then `Ctrl/Cmd+V`) always replace generated sentence audio in `ankiConnect.fields.audio`, even when `overwriteAudio` is disabled.
## AI Translation
@@ -287,6 +289,8 @@ Sentence card creation and audio card marking require a non-empty `ankiConnect.i
Trigger with the mine sentence shortcut (`Ctrl/Cmd+S` by default). The card is created directly via AnkiConnect with the sentence, audio, and image filled in.
+The dedicated sentence-card and audio-card shortcuts use the Lapis/Kiku-compatible `Sentence` and `SentenceAudio` fields. This does not affect the configured fields used to enrich normal word cards.
+
To mine multiple subtitle lines as one sentence card, use `Ctrl/Cmd+Shift+S` followed by a digit (1–9) to select how many recent lines to combine.
## Word Card Type (Kiku/Lapis)
diff --git a/docs-site/mining-workflow.md b/docs-site/mining-workflow.md
index ea0d9d42..20470598 100644
--- a/docs-site/mining-workflow.md
+++ b/docs-site/mining-workflow.md
@@ -41,7 +41,7 @@ If you prefer a hands-on approach (animecards-style), you can copy the current s
- For multiple lines: press `Ctrl/Cmd+Shift+C`, then a digit `1`–`9` to select how many recent subtitle lines to combine. The combined text is copied to the clipboard.
3. Press `Ctrl/Cmd+V` to update the last-added card with the clipboard contents plus audio, image, and translation - the same fields auto-update would fill.
-Manual clipboard updates always replace generated sentence audio, even when `ankiConnect.behavior.overwriteAudio` is disabled. The word audio field is left unchanged because the word itself does not change in this flow.
+Manual clipboard updates always replace generated sentence audio in `ankiConnect.fields.audio`, even when `ankiConnect.behavior.overwriteAudio` is disabled. Normal word-card updates use the configured sentence and audio fields even when Lapis or Kiku support is enabled.
This is useful when auto-update is disabled or when you want explicit control over which subtitle line gets attached to the card.
diff --git a/src/anki-integration.test.ts b/src/anki-integration.test.ts
index 5fae3673..2971fb37 100644
--- a/src/anki-integration.test.ts
+++ b/src/anki-integration.test.ts
@@ -608,6 +608,7 @@ test('AnkiIntegration applies ready YouTube cache media to every queued note id'
const integration = new AnkiIntegration(
{
fields: {
+ audio: 'ExpressionAudio',
image: 'Picture',
},
media: {
@@ -661,7 +662,7 @@ test('AnkiIntegration applies ready YouTube cache media to every queued note id'
noteIds.map((noteId) => ({
noteId,
fields: {
- SentenceAudio: { value: '' },
+ ExpressionAudio: { value: '' },
Picture: { value: '' },
},
})),
@@ -946,7 +947,7 @@ test('AnkiIntegration queues YouTube media updates against recovered source URLs
noteInfo: {
noteId: 404,
fields: {
- SentenceAudio: { value: '' },
+ ExpressionAudio: { value: '' },
Picture: { value: '' },
},
},
@@ -958,7 +959,8 @@ test('AnkiIntegration queues YouTube media updates against recovered source URLs
assert.equal(queued, true);
assert.equal(updatedNotes.length, 1);
assert.equal(updatedNotes[0]?.noteId, 404);
- assert.match(updatedNotes[0]?.fields.SentenceAudio ?? '', /^\[sound:audio_/);
+ assert.match(updatedNotes[0]?.fields.ExpressionAudio ?? '', /^\[sound:audio_/);
+ assert.equal(updatedNotes[0]?.fields.SentenceAudio, undefined);
assert.match(updatedNotes[0]?.fields.Picture ?? '', /^
$/);
});
diff --git a/src/anki-integration/pending-youtube-media-queue.ts b/src/anki-integration/pending-youtube-media-queue.ts
index 68abd2c9..0cd09195 100644
--- a/src/anki-integration/pending-youtube-media-queue.ts
+++ b/src/anki-integration/pending-youtube-media-queue.ts
@@ -39,7 +39,6 @@ export interface PendingYoutubeMediaQueueDeps {
startTime: number;
endTime: number;
};
- getResolvedSentenceAudioFieldName: (noteInfo: PendingYoutubeMediaNoteInfo) => string | null;
resolveConfiguredFieldName: (
noteInfo: PendingYoutubeMediaNoteInfo,
...preferredNames: (string | undefined)[]
@@ -136,7 +135,7 @@ export class PendingYoutubeMediaQueue {
startTime: mediaRange.startTime,
endTime: mediaRange.endTime,
label: job.label,
- audioFieldName: this.deps.getResolvedSentenceAudioFieldName(job.noteInfo) ?? undefined,
+ audioFieldName: this.resolveConfiguredAudioFieldName(job.noteInfo) ?? undefined,
imageFieldName:
this.deps.resolveConfiguredFieldName(
job.noteInfo,
@@ -247,6 +246,14 @@ export class PendingYoutubeMediaQueue {
return matched;
}
+ private resolveConfiguredAudioFieldName(noteInfo: PendingYoutubeMediaNoteInfo): string | null {
+ const config = this.deps.getConfig();
+ return this.deps.resolveConfiguredFieldName(
+ noteInfo,
+ config.fields?.audio ?? DEFAULT_ANKI_CONNECT_CONFIG.fields.audio,
+ );
+ }
+
private async applyUpdate(
job: PendingYoutubeMediaUpdate,
cachedPath: string,
@@ -283,7 +290,7 @@ export class PendingYoutubeMediaQueue {
if (audioBuffer) {
await this.deps.client.storeMediaFile(audioFilename, audioBuffer);
const audioField =
- job.audioFieldName || this.deps.getResolvedSentenceAudioFieldName(noteInfo) || null;
+ job.audioFieldName || this.resolveConfiguredAudioFieldName(noteInfo) || null;
if (audioField) {
const existingAudio = noteInfo.fields[audioField]?.value || '';
mediaFields[audioField] = this.deps.mergeFieldValue(