From 854b8fb6b6d7ffd0d7cc268c790a288a6ddeac6a Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 15 Feb 2026 00:03:38 -0800 Subject: [PATCH] feat: add manual known-word cache refresh path - Add CLI command flag with non-GUI dispatch flow and OSD error handling. - Add runtime integration call and IPC hook so manual refresh works from command runner without app startup. - Add public AnkiIntegration manual refresh API with force refresh semantics and guard reuse. - Preserve default n+1 behavior by fixing config validation for malformed values and adding tests. --- docs/architecture.md | 24 ++- src/anki-integration.test.ts | 154 ++++++++++++++++++ src/anki-integration.ts | 8 +- src/cli/args.test.ts | 5 + src/cli/args.ts | 4 + src/cli/help.test.ts | 1 + src/cli/help.ts | 5 +- src/config/service.ts | 25 ++- src/core/services/anki-jimaku-ipc-service.ts | 5 + src/core/services/anki-jimaku-service.test.ts | 26 +++ src/core/services/anki-jimaku-service.ts | 15 +- src/core/services/cli-command-service.test.ts | 28 ++++ src/core/services/cli-command-service.ts | 11 ++ src/main.ts | 9 + src/main/cli-runtime.ts | 14 +- src/main/dependencies.ts | 2 + src/main/ipc-runtime.ts | 2 + 17 files changed, 315 insertions(+), 23 deletions(-) create mode 100644 src/anki-integration.test.ts diff --git a/docs/architecture.md b/docs/architecture.md index c20345f..e30a243 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -16,11 +16,23 @@ SubMiner uses a service-oriented Electron architecture with a composition-orient ```text src/ - main.ts # Composition root — lifecycle wiring and state ownership + main.ts # Entry point — delegates to src/main/ composition modules preload.ts # Electron preload bridge types.ts # Shared type definitions + main/ # Composition root modules (extracted from main.ts) + app-lifecycle.ts # Electron lifecycle event registration + cli-runtime.ts # CLI command handling and dispatch + dependencies.ts # Shared dependency builders for IPC/runtime + ipc-mpv-command.ts # MPV command composition helpers + ipc-runtime.ts # IPC channel registration and handlers + overlay-runtime.ts # Overlay window/modal selection and state + overlay-shortcuts-runtime.ts # Overlay keyboard shortcut handling + startup.ts # Startup bootstrap flow (argv/env processing) + startup-lifecycle.ts # App-ready initialization sequence + state.ts # Application runtime state container + subsync-runtime.ts # Subsync command orchestration core/ - services/ # ~55 focused service modules (see below) + services/ # ~60 focused service modules (see below) utils/ # Pure helpers and coercion/config utilities cli/ # CLI parsing and help output config/ # Config schema, defaults, validation, template generation @@ -36,10 +48,10 @@ src/ ### Service Layer (`src/core/services/`) -- **Startup** — `startup-service`, `app-lifecycle-service` -- **Overlay** — `overlay-manager-service`, `overlay-window-service`, `overlay-visibility-service`, `overlay-bridge-service`, `overlay-runtime-init-service` -- **Shortcuts** — `shortcut-service`, `overlay-shortcut-service`, `overlay-shortcut-handler`, `shortcut-fallback-service`, `numeric-shortcut-service` -- **MPV** — `mpv-service`, `mpv-control-service`, `mpv-render-metrics-service` +- **Startup** — `startup-service`, `app-lifecycle-service`, `app-ready-service` +- **Overlay** — `overlay-manager-service`, `overlay-window-service`, `overlay-visibility-service`, `overlay-bridge-service`, `overlay-runtime-init-service`, `overlay-content-measurement-service` +- **Shortcuts** — `shortcut-service`, `overlay-shortcut-service`, `overlay-shortcut-handler`, `shortcut-fallback-service`, `numeric-shortcut-service`, `numeric-shortcut-session-service` +- **MPV** — `mpv-service`, `mpv-control-service`, `mpv-render-metrics-service`, `mpv-transport`, `mpv-protocol`, `mpv-state`, `mpv-properties` - **IPC** — `ipc-service`, `ipc-command-service`, `runtime-options-ipc-service` - **Mining** — `mining-service`, `field-grouping-service`, `field-grouping-overlay-service`, `anki-jimaku-service`, `anki-jimaku-ipc-service` - **Subtitles** — `subtitle-ws-service`, `subtitle-position-service`, `secondary-subtitle-service`, `tokenizer-service` diff --git a/src/anki-integration.test.ts b/src/anki-integration.test.ts new file mode 100644 index 0000000..b32f517 --- /dev/null +++ b/src/anki-integration.test.ts @@ -0,0 +1,154 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import { AnkiIntegration } from "./anki-integration"; + +interface IntegrationTestContext { + integration: AnkiIntegration; + calls: { + findNotes: number; + notesInfo: number; + }; + stateDir: string; +} + +function createIntegrationTestContext( + options: { + highlightEnabled?: boolean; + onFindNotes?: () => Promise; + onNotesInfo?: () => Promise; + stateDirPrefix?: string; + } = {}, +): IntegrationTestContext { + const calls = { + findNotes: 0, + notesInfo: 0, + }; + + const stateDir = fs.mkdtempSync( + path.join(os.tmpdir(), options.stateDirPrefix ?? "subminer-anki-integration-"), + ); + const knownWordCacheStatePath = path.join(stateDir, "known-words-cache.json"); + + const client = { + findNotes: async () => { + calls.findNotes += 1; + if (options.onFindNotes) { + return options.onFindNotes(); + } + return [] as number[]; + }, + notesInfo: async () => { + calls.notesInfo += 1; + if (options.onNotesInfo) { + return options.onNotesInfo(); + } + return [] as unknown[]; + }, + } as { + findNotes: () => Promise; + notesInfo: () => Promise; + }; + + const integration = new AnkiIntegration( + { + nPlusOne: { + highlightEnabled: options.highlightEnabled ?? true, + }, + }, + {} as never, + {} as never, + undefined, + undefined, + undefined, + knownWordCacheStatePath, + ); + + const integrationWithClient = integration as unknown as { + client: { + findNotes: () => Promise; + notesInfo: () => Promise; + }; + }; + integrationWithClient.client = client; + + const privateState = integration as unknown as { + knownWordsScope: string; + knownWordsLastRefreshedAtMs: number; + }; + privateState.knownWordsScope = "is:note"; + privateState.knownWordsLastRefreshedAtMs = Date.now(); + + return { + integration, + calls, + stateDir, + }; +} + +function cleanupIntegrationTestContext(ctx: IntegrationTestContext): void { + fs.rmSync(ctx.stateDir, { recursive: true, force: true }); +} + +test("AnkiIntegration.refreshKnownWordCache bypasses stale checks", async () => { + const ctx = createIntegrationTestContext(); + + try { + await ctx.integration.refreshKnownWordCache(); + + assert.equal(ctx.calls.findNotes, 1); + assert.equal(ctx.calls.notesInfo, 0); + } finally { + cleanupIntegrationTestContext(ctx); + } +}); + +test("AnkiIntegration.refreshKnownWordCache skips work when highlight mode is disabled", async () => { + const ctx = createIntegrationTestContext({ + highlightEnabled: false, + stateDirPrefix: "subminer-anki-integration-disabled-", + }); + + try { + await ctx.integration.refreshKnownWordCache(); + + assert.equal(ctx.calls.findNotes, 0); + assert.equal(ctx.calls.notesInfo, 0); + } finally { + cleanupIntegrationTestContext(ctx); + } +}); + +test("AnkiIntegration.refreshKnownWordCache deduplicates concurrent refreshes", async () => { + let releaseFindNotes: (() => void) | undefined; + const findNotesPromise = new Promise((resolve) => { + releaseFindNotes = resolve; + }); + + const ctx = createIntegrationTestContext({ + onFindNotes: async () => { + await findNotesPromise; + return [] as number[]; + }, + stateDirPrefix: "subminer-anki-integration-concurrent-", + }); + + const first = ctx.integration.refreshKnownWordCache(); + await Promise.resolve(); + const second = ctx.integration.refreshKnownWordCache(); + + if (releaseFindNotes !== undefined) { + releaseFindNotes(); + } + + await Promise.all([first, second]); + + try { + assert.equal(ctx.calls.findNotes, 1); + assert.equal(ctx.calls.notesInfo, 0); + } finally { + cleanupIntegrationTestContext(ctx); + } +}); diff --git a/src/anki-integration.ts b/src/anki-integration.ts index 8c60a31..8897c23 100644 --- a/src/anki-integration.ts +++ b/src/anki-integration.ts @@ -223,7 +223,11 @@ export class AnkiIntegration { } } - private async refreshKnownWords(): Promise { + async refreshKnownWordCache(): Promise { + return this.refreshKnownWords(true); + } + + private async refreshKnownWords(force = false): Promise { if (!this.isKnownWordCacheEnabled()) { log.debug("Known-word cache refresh skipped; feature disabled"); return; @@ -232,7 +236,7 @@ export class AnkiIntegration { log.debug("Known-word cache refresh skipped; already refreshing"); return; } - if (!this.isKnownWordCacheStale()) { + if (!force && !this.isKnownWordCacheStale()) { log.debug("Known-word cache refresh skipped; cache is fresh"); return; } diff --git a/src/cli/args.test.ts b/src/cli/args.test.ts index fb6b49e..5fcb915 100644 --- a/src/cli/args.test.ts +++ b/src/cli/args.test.ts @@ -41,4 +41,9 @@ test("hasExplicitCommand and shouldStartApp preserve command intent", () => { const noCommand = parseArgs(["--verbose"]); assert.equal(hasExplicitCommand(noCommand), false); assert.equal(shouldStartApp(noCommand), false); + + const refreshKnownWords = parseArgs(["--refresh-known-words"]); + assert.equal(refreshKnownWords.help, false); + assert.equal(hasExplicitCommand(refreshKnownWords), true); + assert.equal(shouldStartApp(refreshKnownWords), false); }); diff --git a/src/cli/args.ts b/src/cli/args.ts index d33aa5a..2954087 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -16,6 +16,7 @@ export interface CliArgs { mineSentence: boolean; mineSentenceMultiple: boolean; updateLastCardFromClipboard: boolean; + refreshKnownWords: boolean; toggleSecondarySub: boolean; triggerFieldGrouping: boolean; triggerSubsync: boolean; @@ -55,6 +56,7 @@ export function parseArgs(argv: string[]): CliArgs { mineSentence: false, mineSentenceMultiple: false, updateLastCardFromClipboard: false, + refreshKnownWords: false, toggleSecondarySub: false, triggerFieldGrouping: false, triggerSubsync: false, @@ -100,6 +102,7 @@ export function parseArgs(argv: string[]): CliArgs { else if (arg === "--mine-sentence-multiple") args.mineSentenceMultiple = true; else if (arg === "--update-last-card-from-clipboard") args.updateLastCardFromClipboard = true; + else if (arg === "--refresh-known-words") args.refreshKnownWords = true; else if (arg === "--toggle-secondary-sub") args.toggleSecondarySub = true; else if (arg === "--trigger-field-grouping") args.triggerFieldGrouping = true; @@ -181,6 +184,7 @@ export function hasExplicitCommand(args: CliArgs): boolean { args.mineSentence || args.mineSentenceMultiple || args.updateLastCardFromClipboard || + args.refreshKnownWords || args.toggleSecondarySub || args.triggerFieldGrouping || args.triggerSubsync || diff --git a/src/cli/help.test.ts b/src/cli/help.test.ts index eb2ee80..a4f0923 100644 --- a/src/cli/help.test.ts +++ b/src/cli/help.test.ts @@ -17,4 +17,5 @@ test("printHelp includes configured texthooker port", () => { assert.match(output, /--help\s+Show this help/); assert.match(output, /default: 7777/); + assert.match(output, /--refresh-known-words/); }); diff --git a/src/cli/help.ts b/src/cli/help.ts index 0a92b74..73f3e39 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -18,8 +18,9 @@ SubMiner CLI commands: --copy-subtitle-multiple Start multi-copy mode --mine-sentence Mine sentence card from current subtitle --mine-sentence-multiple Start multi-mine sentence mode - --update-last-card-from-clipboard Update last card from clipboard - --toggle-secondary-sub Cycle secondary subtitle mode + --update-last-card-from-clipboard Update last card from clipboard + --refresh-known-words Refresh known words cache now + --toggle-secondary-sub Cycle secondary subtitle mode --trigger-field-grouping Trigger Kiku field grouping --trigger-subsync Run subtitle sync --mark-audio-card Mark last card as audio card diff --git a/src/config/service.ts b/src/config/service.ts index dfa671e..63ea24f 100644 --- a/src/config/service.ts +++ b/src/config/service.ts @@ -445,9 +445,14 @@ export class ConfigService { : isObject(ac.openRouter) ? ac.openRouter : {}; + const { nPlusOne: _nPlusOneConfigFromAnkiConnect, ...ankiConnectWithoutNPlusOne } = + ac as Record; + resolved.ankiConnect = { ...resolved.ankiConnect, - ...(isObject(ac) ? (ac as Partial) : {}), + ...(isObject(ankiConnectWithoutNPlusOne) + ? (ankiConnectWithoutNPlusOne as Partial) + : {}), fields: { ...resolved.ankiConnect.fields, ...(isObject(ac.fields) @@ -598,17 +603,26 @@ export class ConfigService { behavior.nPlusOneHighlightEnabled, ); if (legacyNPlusOneHighlightEnabled !== undefined) { - resolved.ankiConnect.nPlusOne.highlightEnabled = - legacyNPlusOneHighlightEnabled; + resolved.ankiConnect.nPlusOne.highlightEnabled = + legacyNPlusOneHighlightEnabled; warn( "ankiConnect.behavior.nPlusOneHighlightEnabled", behavior.nPlusOneHighlightEnabled, DEFAULT_CONFIG.ankiConnect.nPlusOne.highlightEnabled, "Legacy key is deprecated; use ankiConnect.nPlusOne.highlightEnabled", ); + } else if (nPlusOneConfig.highlightEnabled !== undefined) { + warn( + "ankiConnect.nPlusOne.highlightEnabled", + nPlusOneConfig.highlightEnabled, + resolved.ankiConnect.nPlusOne.highlightEnabled, + "Expected boolean.", + ); + resolved.ankiConnect.nPlusOne.highlightEnabled = + DEFAULT_CONFIG.ankiConnect.nPlusOne.highlightEnabled; } else { - resolved.ankiConnect.nPlusOne.highlightEnabled = - DEFAULT_CONFIG.ankiConnect.nPlusOne.highlightEnabled; + resolved.ankiConnect.nPlusOne.highlightEnabled = + DEFAULT_CONFIG.ankiConnect.nPlusOne.highlightEnabled; } } @@ -734,6 +748,7 @@ export class ConfigService { resolved.ankiConnect.nPlusOne.decks, "Expected an array of strings.", ); + resolved.ankiConnect.nPlusOne.decks = []; } if ( diff --git a/src/core/services/anki-jimaku-ipc-service.ts b/src/core/services/anki-jimaku-ipc-service.ts index 2da2751..1d9c26c 100644 --- a/src/core/services/anki-jimaku-ipc-service.ts +++ b/src/core/services/anki-jimaku-ipc-service.ts @@ -19,6 +19,7 @@ import { export interface AnkiJimakuIpcDeps { setAnkiConnectEnabled: (enabled: boolean) => void; clearAnkiHistory: () => void; + refreshKnownWords: () => Promise | void; respondFieldGrouping: (choice: KikuFieldGroupingChoice) => void; buildKikuMergePreview: ( request: KikuMergePreviewRequest, @@ -55,6 +56,10 @@ export function registerAnkiJimakuIpcHandlers( deps.clearAnkiHistory(); }); + ipcMain.on("anki:refresh-known-words", async () => { + await deps.refreshKnownWords(); + }); + ipcMain.on( "kiku:field-grouping-respond", (_event: IpcMainEvent, choice: KikuFieldGroupingChoice) => { diff --git a/src/core/services/anki-jimaku-service.test.ts b/src/core/services/anki-jimaku-service.test.ts index 152bfcb..d3e465c 100644 --- a/src/core/services/anki-jimaku-service.test.ts +++ b/src/core/services/anki-jimaku-service.test.ts @@ -107,6 +107,7 @@ test("registerAnkiJimakuIpcRuntimeService provides full handler surface", () => const expected = [ "setAnkiConnectEnabled", "clearAnkiHistory", + "refreshKnownWords", "respondFieldGrouping", "buildKikuMergePreview", "getJimakuMediaInfo", @@ -124,6 +125,31 @@ test("registerAnkiJimakuIpcRuntimeService provides full handler surface", () => } }); +test("refreshKnownWords throws when integration is unavailable", async () => { + const { registered } = createHarness(); + + await assert.rejects( + async () => { + await registered.refreshKnownWords(); + }, + { message: "AnkiConnect integration not enabled" }, + ); +}); + +test("refreshKnownWords delegates to integration", async () => { + const { registered, state } = createHarness(); + let refreshed = 0; + state.ankiIntegration = { + refreshKnownWordCache: async () => { + refreshed += 1; + }, + }; + + await registered.refreshKnownWords(); + + assert.equal(refreshed, 1); +}); + test("setAnkiConnectEnabled disables active integration and broadcasts changes", () => { const { registered, state } = createHarness(); let destroyed = 0; diff --git a/src/core/services/anki-jimaku-service.ts b/src/core/services/anki-jimaku-service.ts index 289f849..64fd2ed 100644 --- a/src/core/services/anki-jimaku-service.ts +++ b/src/core/services/anki-jimaku-service.ts @@ -10,7 +10,11 @@ import { KikuFieldGroupingRequestData, } from "../../types"; import { sortJimakuFiles } from "../../jimaku/utils"; -import { registerAnkiJimakuIpcHandlers } from "./anki-jimaku-ipc-service"; +import type { AnkiJimakuIpcDeps } from "./anki-jimaku-ipc-service"; + +export type RegisterAnkiJimakuIpcRuntimeHandler = ( + deps: AnkiJimakuIpcDeps, +) => void; interface MpvClientLike { connected: boolean; @@ -60,7 +64,7 @@ export interface AnkiJimakuIpcRuntimeOptions { export function registerAnkiJimakuIpcRuntimeService( options: AnkiJimakuIpcRuntimeOptions, - registerHandlers: typeof registerAnkiJimakuIpcHandlers = registerAnkiJimakuIpcHandlers, + registerHandlers: RegisterAnkiJimakuIpcRuntimeHandler, ): void { registerHandlers({ setAnkiConnectEnabled: (enabled) => { @@ -108,6 +112,13 @@ export function registerAnkiJimakuIpcRuntimeService( console.log("AnkiConnect subtitle timing history cleared"); } }, + refreshKnownWords: async () => { + const integration = options.getAnkiIntegration(); + if (!integration) { + throw new Error("AnkiConnect integration not enabled"); + } + await integration.refreshKnownWordCache(); + }, respondFieldGrouping: (choice) => { const resolver = options.getFieldGroupingResolver(); if (resolver) { diff --git a/src/core/services/cli-command-service.test.ts b/src/core/services/cli-command-service.test.ts index a0fc848..41ba87f 100644 --- a/src/core/services/cli-command-service.test.ts +++ b/src/core/services/cli-command-service.test.ts @@ -26,6 +26,7 @@ function makeArgs(overrides: Partial = {}): CliArgs { triggerFieldGrouping: false, triggerSubsync: false, markAudioCard: false, + refreshKnownWords: false, openRuntimeOptions: false, texthooker: false, help: false, @@ -106,6 +107,9 @@ function createDeps(overrides: Partial = {}) { updateLastCardFromClipboard: async () => { calls.push("updateLastCardFromClipboard"); }, + refreshKnownWords: async () => { + calls.push("refreshKnownWords"); + }, cycleSecondarySubMode: () => { calls.push("cycleSecondarySubMode"); }, @@ -288,3 +292,27 @@ test("handleCliCommandService handles visibility and utility command dispatches" ); } }); + +test("handleCliCommandService runs refresh-known-words command", () => { + const { deps, calls } = createDeps(); + + handleCliCommandService(makeArgs({ refreshKnownWords: true }), "initial", deps); + + assert.ok(calls.includes("refreshKnownWords")); +}); + +test("handleCliCommandService reports async refresh-known-words errors to OSD", async () => { + const { deps, calls, osd } = createDeps({ + refreshKnownWords: async () => { + throw new Error("refresh boom"); + }, + }); + + handleCliCommandService(makeArgs({ refreshKnownWords: true }), "initial", deps); + await new Promise((resolve) => setImmediate(resolve)); + + assert.ok( + calls.some((value) => value.startsWith("error:refreshKnownWords failed:")), + ); + assert.ok(osd.some((value) => value.includes("Refresh known words failed: refresh boom"))); +}); diff --git a/src/core/services/cli-command-service.ts b/src/core/services/cli-command-service.ts index f97b78d..939114e 100644 --- a/src/core/services/cli-command-service.ts +++ b/src/core/services/cli-command-service.ts @@ -29,6 +29,7 @@ export interface CliCommandServiceDeps { mineSentenceCard: () => Promise; startPendingMineSentenceMultiple: (timeoutMs: number) => void; updateLastCardFromClipboard: () => Promise; + refreshKnownWords: () => Promise; cycleSecondarySubMode: () => void; triggerFieldGrouping: () => Promise; triggerSubsyncFromConfig: () => Promise; @@ -83,6 +84,7 @@ interface MiningCliRuntime { mineSentenceCard: () => Promise; startPendingMineSentenceMultiple: (timeoutMs: number) => void; updateLastCardFromClipboard: () => Promise; + refreshKnownWords: () => Promise; triggerFieldGrouping: () => Promise; triggerSubsyncFromConfig: () => Promise; markLastCardAsAudioCard: () => Promise; @@ -159,6 +161,7 @@ export function createCliCommandDepsRuntimeService( startPendingMineSentenceMultiple: options.mining.startPendingMineSentenceMultiple, updateLastCardFromClipboard: options.mining.updateLastCardFromClipboard, + refreshKnownWords: options.mining.refreshKnownWords, cycleSecondarySubMode: options.ui.cycleSecondarySubMode, triggerFieldGrouping: options.mining.triggerFieldGrouping, triggerSubsyncFromConfig: options.mining.triggerSubsyncFromConfig, @@ -208,6 +211,7 @@ export function handleCliCommandService( args.mineSentence || args.mineSentenceMultiple || args.updateLastCardFromClipboard || + args.refreshKnownWords || args.toggleSecondarySub || args.triggerFieldGrouping || args.triggerSubsync || @@ -295,6 +299,13 @@ export function handleCliCommandService( "updateLastCardFromClipboard", "Update failed", ); + } else if (args.refreshKnownWords) { + runAsyncWithOsd( + () => deps.refreshKnownWords(), + deps, + "refreshKnownWords", + "Refresh known words failed", + ); } else if (args.toggleSecondarySub) { deps.cycleSecondarySubMode(); } else if (args.triggerFieldGrouping) { diff --git a/src/main.ts b/src/main.ts index 7a68cb6..16b2275 100644 --- a/src/main.ts +++ b/src/main.ts @@ -704,6 +704,7 @@ function handleCliCommand( startPendingMineSentenceMultiple: (timeoutMs: number) => startPendingMineSentenceMultiple(timeoutMs), updateLastCardFromClipboard: () => updateLastCardFromClipboard(), + refreshKnownWordCache: () => refreshKnownWordCache(), triggerFieldGrouping: () => triggerFieldGrouping(), triggerSubsyncFromConfig: () => triggerSubsyncFromConfig(), markLastCardAsAudioCard: () => markLastCardAsAudioCard(), @@ -1110,6 +1111,14 @@ async function updateLastCardFromClipboard(): Promise { ); } +async function refreshKnownWordCache(): Promise { + if (!appState.ankiIntegration) { + throw new Error("AnkiConnect integration not enabled"); + } + + await appState.ankiIntegration.refreshKnownWordCache(); +} + async function triggerFieldGrouping(): Promise { await triggerFieldGroupingService( { diff --git a/src/main/cli-runtime.ts b/src/main/cli-runtime.ts index 139c964..df7de43 100644 --- a/src/main/cli-runtime.ts +++ b/src/main/cli-runtime.ts @@ -22,6 +22,7 @@ export interface CliCommandRuntimeServiceContext { mineSentenceCard: () => Promise; startPendingMineSentenceMultiple: (timeoutMs: number) => void; updateLastCardFromClipboard: () => Promise; + refreshKnownWordCache: () => Promise; triggerFieldGrouping: () => Promise; triggerSubsyncFromConfig: () => Promise; markLastCardAsAudioCard: () => Promise; @@ -70,12 +71,13 @@ function createCliCommandDepsFromContext( mining: { copyCurrentSubtitle: context.copyCurrentSubtitle, startPendingMultiCopy: context.startPendingMultiCopy, - mineSentenceCard: context.mineSentenceCard, - startPendingMineSentenceMultiple: context.startPendingMineSentenceMultiple, - updateLastCardFromClipboard: context.updateLastCardFromClipboard, - triggerFieldGrouping: context.triggerFieldGrouping, - triggerSubsyncFromConfig: context.triggerSubsyncFromConfig, - markLastCardAsAudioCard: context.markLastCardAsAudioCard, + mineSentenceCard: context.mineSentenceCard, + startPendingMineSentenceMultiple: context.startPendingMineSentenceMultiple, + updateLastCardFromClipboard: context.updateLastCardFromClipboard, + refreshKnownWords: context.refreshKnownWordCache, + triggerFieldGrouping: context.triggerFieldGrouping, + triggerSubsyncFromConfig: context.triggerSubsyncFromConfig, + markLastCardAsAudioCard: context.markLastCardAsAudioCard, }, ui: { openYomitanSettings: context.openYomitanSettings, diff --git a/src/main/dependencies.ts b/src/main/dependencies.ts index 3313192..3a45639 100644 --- a/src/main/dependencies.ts +++ b/src/main/dependencies.ts @@ -145,6 +145,7 @@ export interface CliCommandRuntimeServiceDepsParams { CliCommandDepsRuntimeOptions["mining"]["startPendingMineSentenceMultiple"]; updateLastCardFromClipboard: CliCommandDepsRuntimeOptions["mining"]["updateLastCardFromClipboard"]; + refreshKnownWords: CliCommandDepsRuntimeOptions["mining"]["refreshKnownWords"]; triggerFieldGrouping: CliCommandDepsRuntimeOptions["mining"]["triggerFieldGrouping"]; triggerSubsyncFromConfig: CliCommandDepsRuntimeOptions["mining"]["triggerSubsyncFromConfig"]; @@ -273,6 +274,7 @@ export function createCliCommandRuntimeServiceDeps( mineSentenceCard: params.mining.mineSentenceCard, startPendingMineSentenceMultiple: params.mining.startPendingMineSentenceMultiple, updateLastCardFromClipboard: params.mining.updateLastCardFromClipboard, + refreshKnownWords: params.mining.refreshKnownWords, triggerFieldGrouping: params.mining.triggerFieldGrouping, triggerSubsyncFromConfig: params.mining.triggerSubsyncFromConfig, markLastCardAsAudioCard: params.mining.markLastCardAsAudioCard, diff --git a/src/main/ipc-runtime.ts b/src/main/ipc-runtime.ts index a98e040..8a2a9db 100644 --- a/src/main/ipc-runtime.ts +++ b/src/main/ipc-runtime.ts @@ -3,6 +3,7 @@ import { registerAnkiJimakuIpcRuntimeService, registerIpcHandlersService, } from "../core/services"; +import { registerAnkiJimakuIpcHandlers } from "../core/services/anki-jimaku-ipc-service"; import { createAnkiJimakuIpcRuntimeServiceDeps, AnkiJimakuIpcRuntimeServiceDepsParams, @@ -34,6 +35,7 @@ export function registerAnkiJimakuIpcRuntimeServices( ): void { registerAnkiJimakuIpcRuntimeService( createAnkiJimakuIpcRuntimeServiceDeps(params), + registerAnkiJimakuIpcHandlers, ); }