refactor: extract yomitan runtime wiring from main

This commit is contained in:
2026-02-20 02:00:16 -08:00
parent 062677dcc5
commit 3aeb10ae61
5 changed files with 207 additions and 46 deletions

View File

@@ -6,7 +6,7 @@ Read first. Keep concise.
| ------------ | -------------- | ---------------------------------------------------- | --------- | ------------------------------------- | ---------------------- | | ------------ | -------------- | ---------------------------------------------------- | --------- | ------------------------------------- | ---------------------- |
| `codex-generate-minecard-image-20260220T112900Z-vsxr` | `codex-generate-minecard-image` | `Generate media fallbacks (GIF) from assets/minecard.webm and wire README/docs fallback markup` | `done` | `docs/subagents/agents/codex-generate-minecard-image-20260220T112900Z-vsxr.md` | `2026-02-20T11:35:30Z` | | `codex-generate-minecard-image-20260220T112900Z-vsxr` | `codex-generate-minecard-image` | `Generate media fallbacks (GIF) from assets/minecard.webm and wire README/docs fallback markup` | `done` | `docs/subagents/agents/codex-generate-minecard-image-20260220T112900Z-vsxr.md` | `2026-02-20T11:35:30Z` |
| `codex-main` | `planner-exec` | `Fix frequency/N+1 regression in plugin --start flow` | `in_progress` | `docs/subagents/agents/codex-main.md` | `2026-02-19T19:36:46Z` | | `codex-main` | `planner-exec` | `Fix frequency/N+1 regression in plugin --start flow` | `in_progress` | `docs/subagents/agents/codex-main.md` | `2026-02-19T19:36:46Z` |
| `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T09:51:57Z` | | `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T09:59:54Z` |
| `codex-config-validation-20260219T172015Z-iiyf` | `codex-config-validation` | `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc` | `completed` | `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md` | `2026-02-19T17:26:17Z` | | `codex-config-validation-20260219T172015Z-iiyf` | `codex-config-validation` | `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc` | `completed` | `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md` | `2026-02-19T17:26:17Z` |
| `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T02:56:34Z` | | `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T02:56:34Z` |
| `codex-anilist-deeplink-20260219T233926Z` | `anilist-deeplink` | `Fix external subminer:// AniList callback handling from browser` | `done` | `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md` | `2026-02-19T23:59:21Z` | | `codex-anilist-deeplink-20260219T233926Z` | `anilist-deeplink` | `Fix external subminer:// AniList callback handling from browser` | `done` | `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md` | `2026-02-19T23:59:21Z` |

View File

@@ -9,6 +9,10 @@
## Current Work (newest first) ## Current Work (newest first)
- [2026-02-20T09:59:54Z] progress: pivot batch completed: extracted Yomitan extension runtime wiring from `src/main.ts` into new module `src/main/runtime/yomitan-extension-runtime.ts` and replaced `main.ts` setup with `createYomitanExtensionRuntime(...)`.
- [2026-02-20T09:59:54Z] progress: added regression coverage for extracted composition in `src/main/runtime/yomitan-extension-runtime.test.ts`; finalized remaining inline `build*MainDepsHandler(),` constructor sites in `src/main.ts` (count now 0).
- [2026-02-20T09:59:54Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + targeted suites pass for `yomitan-extension-runtime*`, `yomitan-extension-loader*`, `mpv-subtitle-render-metrics*`, `overlay-window-layout*`, `mining-actions*`, `ipc-bridge-actions*`, `cli-command-context*`, `startup-warmups*` (30/30).
- [2026-02-20T09:59:54Z] scope: staging `src/main.ts`, new Yomitan runtime module/tests, and subagent bookkeeping only; unrelated workspace deltas remain excluded.
- [2026-02-20T09:51:57Z] progress: completed another 2-slice safe batch (10 normalized sites) by prebuilding finalized deps for stop/run Jellyfin command handlers, app lifecycle runner, initial-args handler, MPV event binding, and numeric/overlay-shortcut related setup. - [2026-02-20T09:51:57Z] progress: completed another 2-slice safe batch (10 normalized sites) by prebuilding finalized deps for stop/run Jellyfin command handlers, app lifecycle runner, initial-args handler, MPV event binding, and numeric/overlay-shortcut related setup.
- [2026-02-20T09:51:57Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + targeted suites pass for `jellyfin-command-dispatch*`, `jellyfin-remote-session-lifecycle`, `app-lifecycle-*`, `initial-args*`, `mpv-main-event-*`, `numeric-shortcut-*`, `overlay-shortcuts-lifecycle*`, and `anki-actions` (39/39). - [2026-02-20T09:51:57Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + targeted suites pass for `jellyfin-command-dispatch*`, `jellyfin-remote-session-lifecycle`, `app-lifecycle-*`, `initial-args*`, `mpv-main-event-*`, `numeric-shortcut-*`, `overlay-shortcuts-lifecycle*`, and `anki-actions` (39/39).
- [2026-02-20T09:51:57Z] scope: committing only `src/main.ts` + subagent bookkeeping files; unrelated workspace noise still excluded. - [2026-02-20T09:51:57Z] scope: committing only `src/main.ts` + subagent bookkeeping files; unrelated workspace noise still excluded.

View File

@@ -428,14 +428,7 @@ import {
createBuildInitializeOverlayRuntimeBootstrapMainDepsHandler, createBuildInitializeOverlayRuntimeBootstrapMainDepsHandler,
createBuildOpenYomitanSettingsMainDepsHandler, createBuildOpenYomitanSettingsMainDepsHandler,
} from './main/runtime/app-runtime-main-deps'; } from './main/runtime/app-runtime-main-deps';
import { import { createYomitanExtensionRuntime } from './main/runtime/yomitan-extension-runtime';
createEnsureYomitanExtensionLoadedHandler,
createLoadYomitanExtensionHandler,
} from './main/runtime/yomitan-extension-loader';
import {
createBuildEnsureYomitanExtensionLoadedMainDepsHandler,
createBuildLoadYomitanExtensionMainDepsHandler,
} from './main/runtime/yomitan-extension-loader-main-deps';
import { createBuildInitializeOverlayRuntimeOptionsHandler } from './main/runtime/overlay-runtime-options'; import { createBuildInitializeOverlayRuntimeOptionsHandler } from './main/runtime/overlay-runtime-options';
import { createBuildInitializeOverlayRuntimeMainDepsHandler } from './main/runtime/overlay-runtime-options-main-deps'; import { createBuildInitializeOverlayRuntimeMainDepsHandler } from './main/runtime/overlay-runtime-options-main-deps';
import { createBuildCliCommandContextDepsHandler } from './main/runtime/cli-command-context-deps'; import { createBuildCliCommandContextDepsHandler } from './main/runtime/cli-command-context-deps';
@@ -2374,8 +2367,10 @@ const buildUpdateMpvSubtitleRenderMetricsMainDepsHandler =
broadcastToOverlayWindows('mpv-subtitle-render-metrics:set', metrics); broadcastToOverlayWindows('mpv-subtitle-render-metrics:set', metrics);
}, },
}); });
const updateMpvSubtitleRenderMetricsMainDeps =
buildUpdateMpvSubtitleRenderMetricsMainDepsHandler();
const updateMpvSubtitleRenderMetricsRuntime = createUpdateMpvSubtitleRenderMetricsHandler( const updateMpvSubtitleRenderMetricsRuntime = createUpdateMpvSubtitleRenderMetricsHandler(
buildUpdateMpvSubtitleRenderMetricsMainDepsHandler(), updateMpvSubtitleRenderMetricsMainDeps,
); );
function updateMpvSubtitleRenderMetrics(patch: Partial<MpvSubtitleRenderMetrics>): void { function updateMpvSubtitleRenderMetrics(patch: Partial<MpvSubtitleRenderMetrics>): void {
@@ -2525,16 +2520,17 @@ const buildEnforceOverlayLayerOrderMainDepsHandler =
getInvisibleWindow: () => overlayManager.getInvisibleWindow(), getInvisibleWindow: () => overlayManager.getInvisibleWindow(),
ensureOverlayWindowLevel: (window) => ensureOverlayWindowLevel(window as BrowserWindow), ensureOverlayWindowLevel: (window) => ensureOverlayWindowLevel(window as BrowserWindow),
}); });
const enforceOverlayLayerOrderMainDeps = buildEnforceOverlayLayerOrderMainDepsHandler();
const enforceOverlayLayerOrder = createEnforceOverlayLayerOrderHandler( const enforceOverlayLayerOrder = createEnforceOverlayLayerOrderHandler(
buildEnforceOverlayLayerOrderMainDepsHandler(), enforceOverlayLayerOrderMainDeps,
); );
async function loadYomitanExtension(): Promise<Extension | null> { async function loadYomitanExtension(): Promise<Extension | null> {
return loadYomitanExtensionHandler(); return yomitanExtensionRuntime.loadYomitanExtension();
} }
async function ensureYomitanExtensionLoaded(): Promise<Extension | null> { async function ensureYomitanExtensionLoaded(): Promise<Extension | null> {
return ensureYomitanExtensionLoadedHandler(); return yomitanExtensionRuntime.ensureYomitanExtensionLoaded();
} }
function createOverlayWindow(kind: 'visible' | 'invisible'): BrowserWindow { function createOverlayWindow(kind: 'visible' | 'invisible'): BrowserWindow {
@@ -2811,8 +2807,9 @@ const buildRefreshKnownWordCacheMainDepsHandler = createBuildRefreshKnownWordCac
getAnkiIntegration: () => appState.ankiIntegration, getAnkiIntegration: () => appState.ankiIntegration,
missingIntegrationMessage: 'AnkiConnect integration not enabled', missingIntegrationMessage: 'AnkiConnect integration not enabled',
}); });
const refreshKnownWordCacheMainDeps = buildRefreshKnownWordCacheMainDepsHandler();
const refreshKnownWordCacheHandler = createRefreshKnownWordCacheHandler( const refreshKnownWordCacheHandler = createRefreshKnownWordCacheHandler(
buildRefreshKnownWordCacheMainDepsHandler(), refreshKnownWordCacheMainDeps,
); );
const buildTriggerFieldGroupingMainDepsHandler = createBuildTriggerFieldGroupingMainDepsHandler({ const buildTriggerFieldGroupingMainDepsHandler = createBuildTriggerFieldGroupingMainDepsHandler({
@@ -2820,8 +2817,9 @@ const buildTriggerFieldGroupingMainDepsHandler = createBuildTriggerFieldGrouping
showMpvOsd: (text) => showMpvOsd(text), showMpvOsd: (text) => showMpvOsd(text),
triggerFieldGroupingCore, triggerFieldGroupingCore,
}); });
const triggerFieldGroupingMainDeps = buildTriggerFieldGroupingMainDepsHandler();
const triggerFieldGroupingHandler = createTriggerFieldGroupingHandler( const triggerFieldGroupingHandler = createTriggerFieldGroupingHandler(
buildTriggerFieldGroupingMainDepsHandler(), triggerFieldGroupingMainDeps,
); );
const buildMarkLastCardAsAudioCardMainDepsHandler = const buildMarkLastCardAsAudioCardMainDepsHandler =
@@ -2830,8 +2828,10 @@ const buildMarkLastCardAsAudioCardMainDepsHandler =
showMpvOsd: (text) => showMpvOsd(text), showMpvOsd: (text) => showMpvOsd(text),
markLastCardAsAudioCardCore, markLastCardAsAudioCardCore,
}); });
const markLastCardAsAudioCardMainDeps =
buildMarkLastCardAsAudioCardMainDepsHandler();
const markLastCardAsAudioCardHandler = createMarkLastCardAsAudioCardHandler( const markLastCardAsAudioCardHandler = createMarkLastCardAsAudioCardHandler(
buildMarkLastCardAsAudioCardMainDepsHandler(), markLastCardAsAudioCardMainDeps,
); );
const buildMineSentenceCardMainDepsHandler = createBuildMineSentenceCardMainDepsHandler({ const buildMineSentenceCardMainDepsHandler = createBuildMineSentenceCardMainDepsHandler({
@@ -2851,8 +2851,9 @@ const buildHandleMultiCopyDigitMainDepsHandler = createBuildHandleMultiCopyDigit
showMpvOsd: (text) => showMpvOsd(text), showMpvOsd: (text) => showMpvOsd(text),
handleMultiCopyDigitCore, handleMultiCopyDigitCore,
}); });
const handleMultiCopyDigitMainDeps = buildHandleMultiCopyDigitMainDepsHandler();
const handleMultiCopyDigitHandler = createHandleMultiCopyDigitHandler( const handleMultiCopyDigitHandler = createHandleMultiCopyDigitHandler(
buildHandleMultiCopyDigitMainDepsHandler(), handleMultiCopyDigitMainDeps,
); );
const buildCopyCurrentSubtitleMainDepsHandler = createBuildCopyCurrentSubtitleMainDepsHandler({ const buildCopyCurrentSubtitleMainDepsHandler = createBuildCopyCurrentSubtitleMainDepsHandler({
@@ -2861,8 +2862,9 @@ const buildCopyCurrentSubtitleMainDepsHandler = createBuildCopyCurrentSubtitleMa
showMpvOsd: (text) => showMpvOsd(text), showMpvOsd: (text) => showMpvOsd(text),
copyCurrentSubtitleCore, copyCurrentSubtitleCore,
}); });
const copyCurrentSubtitleMainDeps = buildCopyCurrentSubtitleMainDepsHandler();
const copyCurrentSubtitleHandler = createCopyCurrentSubtitleHandler( const copyCurrentSubtitleHandler = createCopyCurrentSubtitleHandler(
buildCopyCurrentSubtitleMainDepsHandler(), copyCurrentSubtitleMainDeps,
); );
const buildHandleMineSentenceDigitMainDepsHandler = const buildHandleMineSentenceDigitMainDepsHandler =
@@ -2879,8 +2881,9 @@ const buildHandleMineSentenceDigitMainDepsHandler =
}, },
handleMineSentenceDigitCore, handleMineSentenceDigitCore,
}); });
const handleMineSentenceDigitMainDeps = buildHandleMineSentenceDigitMainDepsHandler();
const handleMineSentenceDigitHandler = createHandleMineSentenceDigitHandler( const handleMineSentenceDigitHandler = createHandleMineSentenceDigitHandler(
buildHandleMineSentenceDigitMainDepsHandler(), handleMineSentenceDigitMainDeps,
); );
const buildSetVisibleOverlayVisibleMainDepsHandler = const buildSetVisibleOverlayVisibleMainDepsHandler =
createBuildSetVisibleOverlayVisibleMainDepsHandler({ createBuildSetVisibleOverlayVisibleMainDepsHandler({
@@ -2899,8 +2902,10 @@ const buildSetVisibleOverlayVisibleMainDepsHandler =
setMpvSubVisibilityRuntime(appState.mpvClient, mpvSubVisible); setMpvSubVisibilityRuntime(appState.mpvClient, mpvSubVisible);
}, },
}); });
const setVisibleOverlayVisibleMainDeps =
buildSetVisibleOverlayVisibleMainDepsHandler();
const setVisibleOverlayVisibleHandler = createSetVisibleOverlayVisibleHandler( const setVisibleOverlayVisibleHandler = createSetVisibleOverlayVisibleHandler(
buildSetVisibleOverlayVisibleMainDepsHandler(), setVisibleOverlayVisibleMainDeps,
); );
const buildSetInvisibleOverlayVisibleMainDepsHandler = const buildSetInvisibleOverlayVisibleMainDepsHandler =
@@ -2913,16 +2918,19 @@ const buildSetInvisibleOverlayVisibleMainDepsHandler =
syncInvisibleOverlayMousePassthrough: () => syncInvisibleOverlayMousePassthrough: () =>
overlayVisibilityRuntime.syncInvisibleOverlayMousePassthrough(), overlayVisibilityRuntime.syncInvisibleOverlayMousePassthrough(),
}); });
const setInvisibleOverlayVisibleMainDeps =
buildSetInvisibleOverlayVisibleMainDepsHandler();
const setInvisibleOverlayVisibleHandler = createSetInvisibleOverlayVisibleHandler( const setInvisibleOverlayVisibleHandler = createSetInvisibleOverlayVisibleHandler(
buildSetInvisibleOverlayVisibleMainDepsHandler(), setInvisibleOverlayVisibleMainDeps,
); );
const buildToggleVisibleOverlayMainDepsHandler = createBuildToggleVisibleOverlayMainDepsHandler({ const buildToggleVisibleOverlayMainDepsHandler = createBuildToggleVisibleOverlayMainDepsHandler({
getVisibleOverlayVisible: () => overlayManager.getVisibleOverlayVisible(), getVisibleOverlayVisible: () => overlayManager.getVisibleOverlayVisible(),
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible), setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
}); });
const toggleVisibleOverlayMainDeps = buildToggleVisibleOverlayMainDepsHandler();
const toggleVisibleOverlayHandler = createToggleVisibleOverlayHandler( const toggleVisibleOverlayHandler = createToggleVisibleOverlayHandler(
buildToggleVisibleOverlayMainDepsHandler(), toggleVisibleOverlayMainDeps,
); );
const buildToggleInvisibleOverlayMainDepsHandler = const buildToggleInvisibleOverlayMainDepsHandler =
@@ -2930,15 +2938,18 @@ const buildToggleInvisibleOverlayMainDepsHandler =
getInvisibleOverlayVisible: () => overlayManager.getInvisibleOverlayVisible(), getInvisibleOverlayVisible: () => overlayManager.getInvisibleOverlayVisible(),
setInvisibleOverlayVisible: (visible) => setInvisibleOverlayVisible(visible), setInvisibleOverlayVisible: (visible) => setInvisibleOverlayVisible(visible),
}); });
const toggleInvisibleOverlayMainDeps =
buildToggleInvisibleOverlayMainDepsHandler();
const toggleInvisibleOverlayHandler = createToggleInvisibleOverlayHandler( const toggleInvisibleOverlayHandler = createToggleInvisibleOverlayHandler(
buildToggleInvisibleOverlayMainDepsHandler(), toggleInvisibleOverlayMainDeps,
); );
const buildSetOverlayVisibleMainDepsHandler = createBuildSetOverlayVisibleMainDepsHandler({ const buildSetOverlayVisibleMainDepsHandler = createBuildSetOverlayVisibleMainDepsHandler({
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible), setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
}); });
const setOverlayVisibleMainDeps = buildSetOverlayVisibleMainDepsHandler();
const setOverlayVisibleHandler = createSetOverlayVisibleHandler( const setOverlayVisibleHandler = createSetOverlayVisibleHandler(
buildSetOverlayVisibleMainDepsHandler(), setOverlayVisibleMainDeps,
); );
const buildToggleOverlayMainDepsHandler = createBuildToggleOverlayMainDepsHandler({ const buildToggleOverlayMainDepsHandler = createBuildToggleOverlayMainDepsHandler({
@@ -2950,8 +2961,10 @@ const buildHandleOverlayModalClosedMainDepsHandler =
createBuildHandleOverlayModalClosedMainDepsHandler({ createBuildHandleOverlayModalClosedMainDepsHandler({
handleOverlayModalClosedRuntime: (modal) => overlayModalRuntime.handleOverlayModalClosed(modal), handleOverlayModalClosedRuntime: (modal) => overlayModalRuntime.handleOverlayModalClosed(modal),
}); });
const handleOverlayModalClosedMainDeps =
buildHandleOverlayModalClosedMainDepsHandler();
const handleOverlayModalClosedHandler = createHandleOverlayModalClosedHandler( const handleOverlayModalClosedHandler = createHandleOverlayModalClosedHandler(
buildHandleOverlayModalClosedMainDepsHandler(), handleOverlayModalClosedMainDeps,
); );
const buildAppendClipboardVideoToQueueMainDepsHandler = const buildAppendClipboardVideoToQueueMainDepsHandler =
@@ -2964,8 +2977,10 @@ const buildAppendClipboardVideoToQueueMainDepsHandler =
sendMpvCommandRuntime(appState.mpvClient, command); sendMpvCommandRuntime(appState.mpvClient, command);
}, },
}); });
const appendClipboardVideoToQueueMainDeps =
buildAppendClipboardVideoToQueueMainDepsHandler();
const appendClipboardVideoToQueueHandler = createAppendClipboardVideoToQueueHandler( const appendClipboardVideoToQueueHandler = createAppendClipboardVideoToQueueHandler(
buildAppendClipboardVideoToQueueMainDepsHandler(), appendClipboardVideoToQueueMainDeps,
); );
const buildMpvCommandFromIpcRuntimeMainDepsHandler = const buildMpvCommandFromIpcRuntimeMainDepsHandler =
@@ -2993,18 +3008,23 @@ const buildMpvCommandFromIpcRuntimeMainDepsHandler =
const buildHandleMpvCommandFromIpcMainDepsHandler = const buildHandleMpvCommandFromIpcMainDepsHandler =
createBuildHandleMpvCommandFromIpcMainDepsHandler({ createBuildHandleMpvCommandFromIpcMainDepsHandler({
handleMpvCommandFromIpcRuntime, handleMpvCommandFromIpcRuntime,
buildMpvCommandDeps: () => buildMpvCommandFromIpcRuntimeMainDepsHandler(), buildMpvCommandDeps: () => mpvCommandFromIpcRuntimeMainDeps,
}); });
const mpvCommandFromIpcRuntimeMainDeps = buildMpvCommandFromIpcRuntimeMainDepsHandler();
const handleMpvCommandFromIpcMainDeps =
buildHandleMpvCommandFromIpcMainDepsHandler();
const handleMpvCommandFromIpcHandler = createHandleMpvCommandFromIpcHandler( const handleMpvCommandFromIpcHandler = createHandleMpvCommandFromIpcHandler(
buildHandleMpvCommandFromIpcMainDepsHandler(), handleMpvCommandFromIpcMainDeps,
); );
const buildRunSubsyncManualFromIpcMainDepsHandler = const buildRunSubsyncManualFromIpcMainDepsHandler =
createBuildRunSubsyncManualFromIpcMainDepsHandler({ createBuildRunSubsyncManualFromIpcMainDepsHandler({
runManualFromIpc: (request: SubsyncManualRunRequest) => subsyncRuntime.runManualFromIpc(request), runManualFromIpc: (request: SubsyncManualRunRequest) => subsyncRuntime.runManualFromIpc(request),
}); });
const runSubsyncManualFromIpcMainDeps =
buildRunSubsyncManualFromIpcMainDepsHandler();
const runSubsyncManualFromIpcHandler = createRunSubsyncManualFromIpcHandler( const runSubsyncManualFromIpcHandler = createRunSubsyncManualFromIpcHandler(
buildRunSubsyncManualFromIpcMainDepsHandler(), runSubsyncManualFromIpcMainDeps,
); );
const buildCliCommandContextMainDepsHandler = createBuildCliCommandContextMainDepsHandler({ const buildCliCommandContextMainDepsHandler = createBuildCliCommandContextMainDepsHandler({
appState, appState,
@@ -3048,8 +3068,9 @@ const buildCliCommandContextMainDepsHandler = createBuildCliCommandContextMainDe
logWarn: (message: string) => logger.warn(message), logWarn: (message: string) => logger.warn(message),
logError: (message: string, err: unknown) => logger.error(message, err), logError: (message: string, err: unknown) => logger.error(message, err),
}); });
const cliCommandContextMainDeps = buildCliCommandContextMainDepsHandler();
const buildCliCommandContextDepsHandler = createBuildCliCommandContextDepsHandler( const buildCliCommandContextDepsHandler = createBuildCliCommandContextDepsHandler(
buildCliCommandContextMainDepsHandler(), cliCommandContextMainDeps,
); );
const createOverlayWindowHandler = createCreateOverlayWindowHandler<BrowserWindow>( const createOverlayWindowHandler = createCreateOverlayWindowHandler<BrowserWindow>(
createBuildCreateOverlayWindowMainDepsHandler<BrowserWindow>({ createBuildCreateOverlayWindowMainDepsHandler<BrowserWindow>({
@@ -3137,7 +3158,7 @@ const destroyTrayHandler = createDestroyTrayHandler(
}, },
})(), })(),
); );
const buildLoadYomitanExtensionMainDepsHandler = createBuildLoadYomitanExtensionMainDepsHandler({ const yomitanExtensionRuntime = createYomitanExtensionRuntime({
loadYomitanExtensionCore, loadYomitanExtensionCore,
userDataPath: USER_DATA_PATH, userDataPath: USER_DATA_PATH,
getYomitanParserWindow: () => appState.yomitanParserWindow, getYomitanParserWindow: () => appState.yomitanParserWindow,
@@ -3153,22 +3174,12 @@ const buildLoadYomitanExtensionMainDepsHandler = createBuildLoadYomitanExtension
setYomitanExtension: (extension) => { setYomitanExtension: (extension) => {
appState.yomitanExt = extension; appState.yomitanExt = extension;
}, },
});
const loadYomitanExtensionHandler = createLoadYomitanExtensionHandler(
buildLoadYomitanExtensionMainDepsHandler(),
);
const buildEnsureYomitanExtensionLoadedMainDepsHandler =
createBuildEnsureYomitanExtensionLoadedMainDepsHandler({
getYomitanExtension: () => appState.yomitanExt, getYomitanExtension: () => appState.yomitanExt,
getLoadInFlight: () => yomitanLoadInFlight, getLoadInFlight: () => yomitanLoadInFlight,
setLoadInFlight: (promise) => { setLoadInFlight: (promise) => {
yomitanLoadInFlight = promise; yomitanLoadInFlight = promise;
}, },
loadYomitanExtension: () => loadYomitanExtension(),
}); });
const ensureYomitanExtensionLoadedHandler = createEnsureYomitanExtensionLoadedHandler(
buildEnsureYomitanExtensionLoadedMainDepsHandler(),
);
const buildInitializeOverlayRuntimeOptionsHandler = createBuildInitializeOverlayRuntimeOptionsHandler( const buildInitializeOverlayRuntimeOptionsHandler = createBuildInitializeOverlayRuntimeOptionsHandler(
createBuildInitializeOverlayRuntimeMainDepsHandler({ createBuildInitializeOverlayRuntimeMainDepsHandler({
appState, appState,

View File

@@ -0,0 +1,96 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { Extension } from 'electron';
import { createYomitanExtensionRuntime } from './yomitan-extension-runtime';
test('yomitan extension runtime reuses in-flight ensure load and clears it after resolve', async () => {
let extension: Extension | null = null;
let inFlight: Promise<Extension | null> | null = null;
let parserWindow: unknown = null;
let readyPromise: Promise<void> | null = null;
let initPromise: Promise<boolean> | null = null;
let loadCalls = 0;
const releaseLoadState: { releaseLoad: ((value: Extension | null) => void) | null } = {
releaseLoad: null,
};
const runtime = createYomitanExtensionRuntime({
loadYomitanExtensionCore: async (options) => {
loadCalls += 1;
options.setYomitanParserWindow(null);
options.setYomitanParserReadyPromise(Promise.resolve());
options.setYomitanParserInitPromise(Promise.resolve(true));
return await new Promise<Extension | null>((resolve) => {
releaseLoadState.releaseLoad = (value) => {
options.setYomitanExtension(value);
resolve(value);
};
});
},
userDataPath: '/tmp',
getYomitanParserWindow: () => parserWindow as never,
setYomitanParserWindow: (window) => {
parserWindow = window;
},
setYomitanParserReadyPromise: (promise) => {
readyPromise = promise as Promise<void> | null;
},
setYomitanParserInitPromise: (promise) => {
initPromise = promise as Promise<boolean> | null;
},
setYomitanExtension: (next) => {
extension = next;
},
getYomitanExtension: () => extension,
getLoadInFlight: () => inFlight,
setLoadInFlight: (promise) => {
inFlight = promise;
},
});
const first = runtime.ensureYomitanExtensionLoaded();
const second = runtime.ensureYomitanExtensionLoaded();
assert.equal(loadCalls, 1);
assert.ok(inFlight);
assert.equal(parserWindow, null);
assert.ok(readyPromise);
assert.ok(initPromise);
const fakeExtension = { id: 'yomitan' } as Extension;
const releaseLoad = releaseLoadState.releaseLoad;
if (!releaseLoad) {
throw new Error('expected in-flight yomitan load resolver');
}
releaseLoad(fakeExtension);
assert.equal(await first, fakeExtension);
assert.equal(await second, fakeExtension);
assert.equal(extension, fakeExtension);
assert.equal(inFlight, null);
const third = await runtime.ensureYomitanExtensionLoaded();
assert.equal(third, fakeExtension);
assert.equal(loadCalls, 1);
});
test('yomitan extension runtime direct load delegates to core', async () => {
let loadCalls = 0;
const runtime = createYomitanExtensionRuntime({
loadYomitanExtensionCore: async () => {
loadCalls += 1;
return null;
},
userDataPath: '/tmp',
getYomitanParserWindow: () => null,
setYomitanParserWindow: () => {},
setYomitanParserReadyPromise: () => {},
setYomitanParserInitPromise: () => {},
setYomitanExtension: () => {},
getYomitanExtension: () => null,
getLoadInFlight: () => null,
setLoadInFlight: () => {},
});
assert.equal(await runtime.loadYomitanExtension(), null);
assert.equal(loadCalls, 1);
});

View File

@@ -0,0 +1,50 @@
import { createEnsureYomitanExtensionLoadedHandler, createLoadYomitanExtensionHandler } from './yomitan-extension-loader';
import {
createBuildEnsureYomitanExtensionLoadedMainDepsHandler,
createBuildLoadYomitanExtensionMainDepsHandler,
} from './yomitan-extension-loader-main-deps';
type LoadYomitanExtensionMainDeps = Parameters<
typeof createBuildLoadYomitanExtensionMainDepsHandler
>[0];
type EnsureYomitanExtensionLoadedMainDeps = Omit<
Parameters<typeof createBuildEnsureYomitanExtensionLoadedMainDepsHandler>[0],
'loadYomitanExtension'
>;
export type YomitanExtensionRuntimeDeps = LoadYomitanExtensionMainDeps &
EnsureYomitanExtensionLoadedMainDeps;
export function createYomitanExtensionRuntime(deps: YomitanExtensionRuntimeDeps) {
const buildLoadYomitanExtensionMainDepsHandler = createBuildLoadYomitanExtensionMainDepsHandler({
loadYomitanExtensionCore: deps.loadYomitanExtensionCore,
userDataPath: deps.userDataPath,
getYomitanParserWindow: deps.getYomitanParserWindow,
setYomitanParserWindow: deps.setYomitanParserWindow,
setYomitanParserReadyPromise: deps.setYomitanParserReadyPromise,
setYomitanParserInitPromise: deps.setYomitanParserInitPromise,
setYomitanExtension: deps.setYomitanExtension,
});
const loadYomitanExtensionHandler = createLoadYomitanExtensionHandler(
buildLoadYomitanExtensionMainDepsHandler(),
);
const buildEnsureYomitanExtensionLoadedMainDepsHandler =
createBuildEnsureYomitanExtensionLoadedMainDepsHandler({
getYomitanExtension: deps.getYomitanExtension,
getLoadInFlight: deps.getLoadInFlight,
setLoadInFlight: deps.setLoadInFlight,
loadYomitanExtension: () => loadYomitanExtensionHandler(),
});
const ensureYomitanExtensionLoadedHandler = createEnsureYomitanExtensionLoadedHandler(
buildEnsureYomitanExtensionLoadedMainDepsHandler(),
);
return {
loadYomitanExtension: (): Promise<ReturnType<typeof deps.getYomitanExtension>> =>
loadYomitanExtensionHandler(),
ensureYomitanExtensionLoaded: (): Promise<ReturnType<typeof deps.getYomitanExtension>> =>
ensureYomitanExtensionLoadedHandler(),
};
}