Switch known-word cache to incremental sync and doctor refresh

- Load persisted known-word cache on startup; reconcile adds/deletes/edits on timed sync
- Add `knownWords.addMinedWordsImmediately` (default `true`) for immediate mined-word updates
- Route full rebuild to explicit `subminer doctor --refresh-known-words` and expand tests/docs
This commit is contained in:
2026-03-19 19:29:58 -07:00
parent 72d78ba1ca
commit 20f53c0b70
50 changed files with 1130 additions and 119 deletions

View File

@@ -93,3 +93,104 @@ test('runAppReadyRuntime minimal startup skips Yomitan and first-run setup while
assert.deepEqual(calls, ['bootstrap', 'reload-config', 'handle-initial-args']);
});
test('runAppReadyRuntime headless refresh bootstraps Anki runtime without UI startup', async () => {
const calls: string[] = [];
await runAppReadyRuntime({
ensureDefaultConfigBootstrap: () => {
calls.push('bootstrap');
},
loadSubtitlePosition: () => {
calls.push('load-subtitle-position');
},
resolveKeybindings: () => {
calls.push('resolve-keybindings');
},
createMpvClient: () => {
calls.push('create-mpv');
},
reloadConfig: () => {
calls.push('reload-config');
},
getResolvedConfig: () => ({}),
getConfigWarnings: () => [],
logConfigWarning: () => {
calls.push('config-warning');
},
setLogLevel: () => {
calls.push('set-log-level');
},
initRuntimeOptionsManager: () => {
calls.push('init-runtime-options');
},
setSecondarySubMode: () => {
calls.push('set-secondary-sub-mode');
},
defaultSecondarySubMode: 'hover',
defaultWebsocketPort: 0,
defaultAnnotationWebsocketPort: 0,
defaultTexthookerPort: 0,
hasMpvWebsocketPlugin: () => false,
startSubtitleWebsocket: () => {
calls.push('subtitle-ws');
},
startAnnotationWebsocket: () => {
calls.push('annotation-ws');
},
startTexthooker: () => {
calls.push('texthooker');
},
log: () => {
calls.push('log');
},
createMecabTokenizerAndCheck: async () => {
calls.push('mecab');
},
createSubtitleTimingTracker: () => {
calls.push('subtitle-timing');
},
createImmersionTracker: () => {
calls.push('immersion');
},
startJellyfinRemoteSession: async () => {
calls.push('jellyfin');
},
loadYomitanExtension: async () => {
calls.push('load-yomitan');
},
handleFirstRunSetup: async () => {
calls.push('first-run');
},
prewarmSubtitleDictionaries: async () => {
calls.push('prewarm');
},
startBackgroundWarmups: () => {
calls.push('warmups');
},
texthookerOnlyMode: false,
shouldAutoInitializeOverlayRuntimeFromConfig: () => false,
setVisibleOverlayVisible: () => {
calls.push('visible-overlay');
},
initializeOverlayRuntime: () => {
calls.push('init-overlay');
},
runHeadlessInitialCommand: async () => {
calls.push('run-headless-command');
},
handleInitialArgs: () => {
calls.push('handle-initial-args');
},
shouldRunHeadlessInitialCommand: () => true,
shouldUseMinimalStartup: () => false,
shouldSkipHeavyStartup: () => false,
});
assert.deepEqual(calls, [
'bootstrap',
'reload-config',
'init-runtime-options',
'run-headless-command',
]);
});