From 4842359e399e935f1135ec3a13b1d6874eba085e Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 18 Oct 2026 22:14:44 -0700 Subject: [PATCH] fix(hachidori): finish character dictionary imports without keep-alive w - Close lingering sockets on the one-shot dictionary ZIP server so imports report completion as soon as the archive is read - Run test:dictionary:electron in the Linux CI E2E job - Note that annotations and lookups pause during Hachidori imports - Keep only dictionaryBackend in the Dictionary Lookup settings section - Move the requestHachidoriSharing import below the license header --- .github/workflows/ci.yml | 4 ++++ changes/hachidori-backend.md | 1 + docs-site/usage.md | 2 +- docs/workflow/verification.md | 2 +- src/config/settings/registry.ts | 2 +- src/core/services/tokenizer/yomitan-parser-runtime.ts | 3 +++ src/main.ts | 2 +- 7 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 501855eb..04f6861e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,10 @@ jobs: - name: End-to-end suite run: bun run test:e2e + - name: Dictionary backend windows (Linux) + if: runner.os == 'Linux' + run: xvfb-run -a bun run test:dictionary:electron + - name: Upload session logs (on failure) if: failure() uses: actions/upload-artifact@v4 diff --git a/changes/hachidori-backend.md b/changes/hachidori-backend.md index 4dda2eb3..326aca7b 100644 --- a/changes/hachidori-backend.md +++ b/changes/hachidori-backend.md @@ -10,5 +10,6 @@ area: dictionary - First-run setup remembers each backend that finished it, including when reopened for legacy plugin cleanup, so switching back does not repeat setup, and the launcher gates playback on the backend the running app started with. Current incomplete or cancelled setup takes precedence over stale completion history. A running Yomitan session keeps using its external profile until it restarts into Hachidori. - Stats dashboard mining and deck lookup use the selected backend, with the Anki proxy enabled or disabled. Dashboard cards retain the selected history line and media instead of being processed again with the current mpv subtitle, with both Yomitan and Hachidori. Stats cards carry a `SubMiner::Stats` tag so polling also preserves their context. Hachidori word mining supplies dictionary aliases, IDs, and frequency metadata to its native Anki renderer. Settings labels for popup pause and the dictionary deck no longer name Yomitan, and the backend selector sits with the other dictionary settings. - Hachidori scans, dictionary counts, and settings reads wait for the dictionary engine to finish loading or importing instead of caching empty results. +- Character dictionary imports report completion as soon as the archive is read instead of waiting out the temporary server's keep-alive timeout. - First-run setup can link an external Hachidori dictionary host in an app, browser, or Docker container, verify its library, and unlink back to local dictionaries. The optional host controls are collapsed by default and explain which apps or containers must stay running. Unresponsive connection checks time out so setup remains usable. Anki mining, media enrichment, and custom toolbar buttons stay in SubMiner, including when editing buttons while linked. - Hachidori frequency highlighting reuses dictionary-entry ranks and fills missing ranks through its existing API. Entries without a matching definition may remain unranked. The bundled integration is maintained in a pinned fork submodule using upstream HoshiDicts and WASM binaries. diff --git a/docs-site/usage.md b/docs-site/usage.md index ec9cf5b5..06df5556 100644 --- a/docs-site/usage.md +++ b/docs-site/usage.md @@ -24,7 +24,7 @@ The bundled Yomitan is separate from any Yomitan in your browser. It has its own Hachidori is an alternative lookup backend. Set `dictionaryBackend` to `"hachidori"` in settings or `config.jsonc`, then restart SubMiner. Set it back to `"yomitan"` and restart to switch back. -Open Hachidori settings with `Alt+Shift+Y`, the tray's **Open Hachidori Settings**, or `subminer app --hachidori`. Import dictionary ZIPs or use Hachidori's recommended dictionary installer, then set up its Anki template (SubMiner [fills in what it can](/anki-integration#hachidori-settings-from-subminer)). Yomitan and Hachidori keep separate dictionaries and settings. Yomitan profiles, custom Handlebars templates, and `yomitan.externalProfilePath` do not carry over. +Open Hachidori settings with `Alt+Shift+Y`, the tray's **Open Hachidori Settings**, or `subminer app --hachidori`. Import dictionary ZIPs or use Hachidori's recommended dictionary installer, then set up its Anki template (SubMiner [fills in what it can](/anki-integration#hachidori-settings-from-subminer)). Yomitan and Hachidori keep separate dictionaries and settings. Yomitan profiles, custom Handlebars templates, and `yomitan.externalProfilePath` do not carry over. Subtitle annotations and lookups wait while Hachidori imports a dictionary, so large imports pause them until the import finishes. Hachidori uses SubMiner's subtitle scanning, popup pause, controller commands, character dictionaries, and Anki media. Keep the [Anki proxy](/anki-integration#proxy-mode-setup-yomitan-texthooker) on for screenshots and sentence audio. Hachidori's own screen recorder and screenshot capture are off inside SubMiner. `startupWarmups.yomitanExtension` and `subtitleStyle.autoPauseVideoOnYomitanPopup` apply to whichever backend is selected. diff --git a/docs/workflow/verification.md b/docs/workflow/verification.md index 76d338bf..f46ddc5d 100644 --- a/docs/workflow/verification.md +++ b/docs/workflow/verification.md @@ -66,7 +66,7 @@ bun run docs:build Content checks run inside the electron-builder afterPack hook. See the [release guide](../RELEASING.md#package-contents-checks) for the installed-app verification checklist. -- Dictionary backend windows: after a full build, run `xvfb-run -a bun run test:dictionary:electron` on headless Linux. It uses temporary profiles to check both named settings commands, backend session isolation, the overlay's external-link bridge, and Hachidori's native dictionary parser. +- Dictionary backend windows: after a full build, run `xvfb-run -a bun run test:dictionary:electron` on headless Linux (CI runs it in the Linux E2E job). It uses temporary profiles to check both named settings commands, backend session isolation, the overlay's external-link bridge, and Hachidori's native dictionary parser. - Coverage for the maintained source lane: `bun run test:coverage:src` - Deep/local full gate: default handoff gate above diff --git a/src/config/settings/registry.ts b/src/config/settings/registry.ts index 5ef39c88..38ffe31b 100644 --- a/src/config/settings/registry.ts +++ b/src/config/settings/registry.ts @@ -335,7 +335,7 @@ function humanizePath(path: string): string { } function categoryAndSection(path: string): { category: ConfigSettingsCategory; section: string } { - if (path === 'dictionaryBackend' || path.startsWith('hachidori.')) { + if (path === 'dictionaryBackend') { return { category: 'integrations', section: 'Dictionary Lookup' }; } if ( diff --git a/src/core/services/tokenizer/yomitan-parser-runtime.ts b/src/core/services/tokenizer/yomitan-parser-runtime.ts index e8bbf6be..534c282d 100644 --- a/src/core/services/tokenizer/yomitan-parser-runtime.ts +++ b/src/core/services/tokenizer/yomitan-parser-runtime.ts @@ -872,6 +872,9 @@ async function serveDictionaryZipOnce( } return await callback(`http://127.0.0.1:${address.port}${requestPath}`); } finally { + // The extension's fetch keeps its socket open; close() alone would wait out + // Node's keep-alive timeout before the import reports completion. + server.closeAllConnections(); await new Promise((resolve) => server.close(() => resolve())); } } diff --git a/src/main.ts b/src/main.ts index ce558998..8ecf934d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,3 @@ -import { requestHachidoriSharing } from './core/services/tokenizer/yomitan-parser-runtime'; /* SubMiner - All-in-one sentence mining overlay Copyright (C) 2026 sudacode @@ -38,6 +37,7 @@ import { createHachidoriExtensionRuntime, getHachidoriSession, } from './core/services/hachidori-extension'; +import { requestHachidoriSharing } from './core/services/tokenizer/yomitan-parser-runtime'; import { DICTIONARY_EXTERNAL_LINK_CHANNEL, parseDictionaryExternalUrl,