mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor(main): finish TASK-94 composition-root extraction
Move IPC, shortcuts, startup lifecycle, and app-ready assembly behind dedicated runtime composers so main.ts stays focused on boot wiring while preserving behavior and test coverage.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
---
|
||||
id: TASK-94
|
||||
title: Reduce main.ts to thin composition root
|
||||
status: In Progress
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-20 12:06'
|
||||
updated_date: '2026-02-21 03:40'
|
||||
updated_date: '2026-02-21 04:12'
|
||||
labels:
|
||||
- architecture
|
||||
- refactor
|
||||
@@ -44,12 +44,38 @@ priority: high
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 `src/main.ts` is composition-focused (boot/runtime wiring only; no broad deps-builder clusters).
|
||||
- [x] #1 `src/main.ts` is composition-focused (boot/runtime wiring only; no broad deps-builder clusters).
|
||||
- [x] #2 Runtime import paths in `src/main.ts` stay domain-registry oriented (no relapse to per-leaf runtime imports).
|
||||
- [x] #3 `check:main-fanin` passes under updated threshold.
|
||||
- [x] #4 `bun run test:core:dist` passes with no CLI/IPC behavior regressions.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1) Extract IPC composition from src/main.ts into src/main/runtime/composers/ipc-runtime-composer.ts with focused tests (handler deps build, runtime handler creation, registration wiring).
|
||||
2) Rewire src/main.ts IPC command and IPC registration blocks to consume the IPC composer while preserving existing helper wrappers and behavior.
|
||||
3) Extract shortcuts composition from src/main.ts into src/main/runtime/composers/shortcuts-runtime-composer.ts with focused tests (global shortcuts runtime, numeric sessions, overlay shortcuts lifecycle).
|
||||
4) Rewire src/main.ts shortcut runtime block to consume shortcuts composer while preserving downstream callsites.
|
||||
5) Run targeted composer tests + check:main-fanin + test:core:dist; update TASK-94 notes with before/after metrics and finalize AC/status if all green.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
2026-02-21: finished extraction pass by moving IPC command/registration, shortcuts runtime wiring, startup lifecycle wiring, and app-ready startup composition from `src/main.ts` into dedicated composer modules under `src/main/runtime/composers/*`.
|
||||
|
||||
Metrics: `src/main.ts` reduced from 3043 LOC to 2955 LOC in this pass; `check:main-fanin` import lines improved from 99 to 90 while remaining under enforced threshold.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Completed TASK-94 composition-root finish pass by extracting remaining large assembly clusters from `src/main.ts` into dedicated composer modules: IPC runtime (`ipc-runtime-composer`), shortcuts runtime (`shortcuts-runtime-composer`), startup lifecycle (`startup-lifecycle-composer`), and app-ready bootstrap (`app-ready-composer`). Main process behavior was preserved while reducing direct deps-builder orchestration in `main.ts` and keeping runtime wiring through domain/composer boundaries.
|
||||
|
||||
Added focused composer tests for each new module and reran project gates. Verification run: `bun run build`, `bun test src/main/runtime/composers/ipc-runtime-composer.test.ts`, `bun test src/main/runtime/composers/shortcuts-runtime-composer.test.ts`, `bun test src/main/runtime/composers/startup-lifecycle-composer.test.ts`, `bun test src/main/runtime/composers/app-ready-composer.test.ts`, `bun run check:main-fanin`, and `bun run test:core:dist` (all passing).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
|
||||
## Definition of Done
|
||||
<!-- DOD:BEGIN -->
|
||||
- [x] #1 `src/main.ts` LOC and fan-in metrics improve from pre-task baseline and are recorded in task notes.
|
||||
|
||||
@@ -30,3 +30,4 @@ Read first. Keep concise.
|
||||
| `opencode-task95-immersion-tracker-20260221T031846Z-p4k9` | `opencode-task95-immersion-tracker` | `Implement TASK-95 immersion-tracker extraction into focused collaborators and seam tests` | `handoff` | `docs/subagents/agents/opencode-task95-immersion-tracker-20260221T031846Z-p4k9.md` | `2026-02-21T03:26:51Z` |
|
||||
| `opencode-task95-config-20260221T031843Z-m4k9` | `opencode-task95-config` | `Implement TASK-95 config extraction for src/config/service.ts` | `done` | `docs/subagents/agents/opencode-task95-config-20260221T031843Z-m4k9.md` | `2026-02-21T03:26:57Z` |
|
||||
| `codex-task95-anki-20260221T031836Z-6f3e` | `codex-task95-anki` | `Implement TASK-95 anki-integration extraction for field-grouping merge collaborator` | `done` | `docs/subagents/agents/codex-task95-anki-20260221T031836Z-6f3e.md` | `2026-02-21T03:26:55Z` |
|
||||
| `opencode-task-94-20260221T033647Z-7ou2` | `opencode-task-94` | `Finish TASK-94 thin composition root refactor and close acceptance criteria` | `done` | `docs/subagents/agents/opencode-task-94-20260221T033647Z-7ou2.md` | `2026-02-21T04:12:45Z` |
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Agent Session: opencode-task-94-20260221T033647Z-7ou2
|
||||
|
||||
- alias: `opencode-task-94`
|
||||
- mission: `Finish TASK-94 thin composition root refactor and close acceptance criteria`
|
||||
- status: `done`
|
||||
- last_update_utc: `2026-02-21T04:12:45Z`
|
||||
|
||||
## Intent
|
||||
|
||||
- Pull TASK-94 context from Backlog MCP; verify remaining gap.
|
||||
- Use writing-plans skill to produce execution plan.
|
||||
- Use executing-plans skill to implement remaining extraction and verify gates.
|
||||
|
||||
## Planned Files
|
||||
|
||||
- `src/main.ts`
|
||||
- `src/main/runtime/composers/*`
|
||||
- `src/main/runtime/registry/*`
|
||||
- `src/main/runtime/shared/*`
|
||||
- `src/main/runtime/**/*.test.ts`
|
||||
- `backlog/tasks/task-94 - Reduce-main.ts-to-thin-composition-root.md` (via MCP task edits only)
|
||||
|
||||
## Assumptions
|
||||
|
||||
- TASK-94 already has partial progress from prior slice.
|
||||
- Remaining work targets acceptance criterion #1 only.
|
||||
- No commit requested in this run.
|
||||
|
||||
## Activity Log
|
||||
|
||||
- `2026-02-21T03:36:58Z` session start; backlog/task context loaded; preparing planning skill.
|
||||
- `2026-02-21T03:47:00Z` extracted IPC + shortcuts composition into dedicated composers; rewired `src/main.ts`; added composer tests.
|
||||
- `2026-02-21T04:05:00Z` extracted startup-lifecycle + app-ready composition clusters into composer modules; rewired `src/main.ts` lifecycle/app-ready assembly.
|
||||
- `2026-02-21T04:11:57Z` verification complete: `bun run build`, composer tests, `bun run check:main-fanin`, `bun run test:core:dist` all passing.
|
||||
- `2026-02-21T04:12:45Z` TASK-94 updated via Backlog MCP: AC #1 checked, final summary captured, status set to Done.
|
||||
|
||||
## Touched Files
|
||||
|
||||
- `src/main.ts`
|
||||
- `src/main/runtime/composers/ipc-runtime-composer.ts`
|
||||
- `src/main/runtime/composers/ipc-runtime-composer.test.ts`
|
||||
- `src/main/runtime/composers/shortcuts-runtime-composer.ts`
|
||||
- `src/main/runtime/composers/shortcuts-runtime-composer.test.ts`
|
||||
- `src/main/runtime/composers/startup-lifecycle-composer.ts`
|
||||
- `src/main/runtime/composers/startup-lifecycle-composer.test.ts`
|
||||
- `src/main/runtime/composers/app-ready-composer.ts`
|
||||
- `src/main/runtime/composers/app-ready-composer.test.ts`
|
||||
- `docs/plans/2026-02-21-task-94-thin-composition-root-finish.md`
|
||||
@@ -26,3 +26,6 @@ Shared notes. Append-only.
|
||||
- [2026-02-21T03:18:46Z] [opencode-task95-immersion-tracker-20260221T031846Z-p4k9|opencode-task95-immersion-tracker] overlap note: implementing TASK-95 immersion-tracker slice in `src/core/services/immersion-tracker-service.ts` + new `src/core/services/immersion-tracker/*` + seam tests; avoiding backlog file edits.
|
||||
- [2026-02-21T03:26:51Z] [opencode-task95-immersion-tracker-20260221T031846Z-p4k9|opencode-task95-immersion-tracker] completed immersion-tracker slice: extracted reducer/query/maintenance/queue/types collaborators, kept public API stable, added seam tests, and verified via `bun run build && node --test dist/core/services/immersion-tracker-service.test.js`.
|
||||
- [2026-02-21T03:26:57Z] [opencode-task95-config-20260221T031843Z-m4k9|opencode-task95-config] completed config slice: extracted `load/parse/warnings/resolve` collaborators, reduced `src/config/service.ts` to facade, added loader precedence + strict non-mutation + warning determinism seam tests, build+config tests green.
|
||||
- [2026-02-21T03:36:58Z] [opencode-task-94-20260221T033647Z-7ou2|opencode-task-94] starting TASK-94 finish pass: pull backlog context, write+execute plan via writing-plans/executing-plans, and close remaining AC without commit.
|
||||
- [2026-02-21T04:11:57Z] [opencode-task-94-20260221T033647Z-7ou2|opencode-task-94] extracted IPC/shortcuts/startup-lifecycle/app-ready clusters behind composer modules, rewired `src/main.ts`, added focused composer tests, and revalidated build + `check:main-fanin` + `test:core:dist`.
|
||||
- [2026-02-21T04:12:45Z] [opencode-task-94-20260221T033647Z-7ou2|opencode-task-94] TASK-94 finalized in Backlog MCP: AC checklist complete, notes+final summary recorded, status moved to Done.
|
||||
|
||||
764
src/main.ts
764
src/main.ts
File diff suppressed because it is too large
Load Diff
75
src/main/runtime/composers/app-ready-composer.test.ts
Normal file
75
src/main/runtime/composers/app-ready-composer.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { composeAppReadyRuntime } from './app-ready-composer';
|
||||
|
||||
test('composeAppReadyRuntime returns reload/critical/app-ready handlers', () => {
|
||||
const composed = composeAppReadyRuntime({
|
||||
reloadConfigMainDeps: {
|
||||
reloadConfigStrict: () => ({ config: {} as never, warnings: [] }),
|
||||
logInfo: () => {},
|
||||
logWarning: () => {},
|
||||
showDesktopNotification: () => {},
|
||||
startConfigHotReload: () => {},
|
||||
refreshAnilistClientSecretState: async () => {},
|
||||
failHandlers: {
|
||||
logError: () => {},
|
||||
showErrorBox: () => {},
|
||||
quit: () => {},
|
||||
},
|
||||
},
|
||||
criticalConfigErrorMainDeps: {
|
||||
getConfigPath: () => '/tmp/config.jsonc',
|
||||
failHandlers: {
|
||||
logError: () => {},
|
||||
showErrorBox: () => {},
|
||||
quit: () => {},
|
||||
},
|
||||
},
|
||||
appReadyRuntimeMainDeps: {
|
||||
loadSubtitlePosition: () => {},
|
||||
resolveKeybindings: () => {},
|
||||
createMpvClient: () => {},
|
||||
getResolvedConfig: () => ({}) as never,
|
||||
getConfigWarnings: () => [],
|
||||
logConfigWarning: () => {},
|
||||
setLogLevel: () => {},
|
||||
initRuntimeOptionsManager: () => {},
|
||||
setSecondarySubMode: () => {},
|
||||
defaultSecondarySubMode: 'hover',
|
||||
defaultWebsocketPort: 5174,
|
||||
hasMpvWebsocketPlugin: () => false,
|
||||
startSubtitleWebsocket: () => {},
|
||||
log: () => {},
|
||||
createMecabTokenizerAndCheck: async () => {},
|
||||
createSubtitleTimingTracker: () => {},
|
||||
loadYomitanExtension: async () => {},
|
||||
startJellyfinRemoteSession: async () => {},
|
||||
prewarmSubtitleDictionaries: async () => {},
|
||||
startBackgroundWarmups: () => {},
|
||||
texthookerOnlyMode: false,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig: () => false,
|
||||
initializeOverlayRuntime: () => {},
|
||||
handleInitialArgs: () => {},
|
||||
logDebug: () => {},
|
||||
now: () => Date.now(),
|
||||
},
|
||||
immersionTrackerStartupMainDeps: {
|
||||
getResolvedConfig: () => ({}) as never,
|
||||
getConfiguredDbPath: () => '/tmp/immersion.sqlite',
|
||||
createTrackerService: () =>
|
||||
({
|
||||
startSession: () => {},
|
||||
}) as never,
|
||||
setTracker: () => {},
|
||||
getMpvClient: () => null,
|
||||
seedTrackerFromCurrentMedia: () => {},
|
||||
logInfo: () => {},
|
||||
logDebug: () => {},
|
||||
logWarn: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(typeof composed.reloadConfig, 'function');
|
||||
assert.equal(typeof composed.criticalConfigError, 'function');
|
||||
assert.equal(typeof composed.appReadyRuntimeRunner, 'function');
|
||||
});
|
||||
58
src/main/runtime/composers/app-ready-composer.ts
Normal file
58
src/main/runtime/composers/app-ready-composer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createAppReadyRuntimeRunner } from '../../app-lifecycle';
|
||||
import { createBuildAppReadyRuntimeMainDepsHandler } from '../app-ready-main-deps';
|
||||
import {
|
||||
createBuildCriticalConfigErrorMainDepsHandler,
|
||||
createBuildReloadConfigMainDepsHandler,
|
||||
} from '../startup-config-main-deps';
|
||||
import { createCriticalConfigErrorHandler, createReloadConfigHandler } from '../startup-config';
|
||||
import { createBuildImmersionTrackerStartupMainDepsHandler } from '../immersion-startup-main-deps';
|
||||
import { createImmersionTrackerStartupHandler } from '../immersion-startup';
|
||||
|
||||
type ReloadConfigMainDeps = Parameters<typeof createBuildReloadConfigMainDepsHandler>[0];
|
||||
type CriticalConfigErrorMainDeps = Parameters<
|
||||
typeof createBuildCriticalConfigErrorMainDepsHandler
|
||||
>[0];
|
||||
type AppReadyRuntimeMainDeps = Parameters<typeof createBuildAppReadyRuntimeMainDepsHandler>[0];
|
||||
|
||||
export type AppReadyComposerOptions = {
|
||||
reloadConfigMainDeps: ReloadConfigMainDeps;
|
||||
criticalConfigErrorMainDeps: CriticalConfigErrorMainDeps;
|
||||
appReadyRuntimeMainDeps: Omit<AppReadyRuntimeMainDeps, 'reloadConfig' | 'onCriticalConfigErrors'>;
|
||||
immersionTrackerStartupMainDeps: Parameters<
|
||||
typeof createBuildImmersionTrackerStartupMainDepsHandler
|
||||
>[0];
|
||||
};
|
||||
|
||||
export type AppReadyComposerResult = {
|
||||
reloadConfig: ReturnType<typeof createReloadConfigHandler>;
|
||||
criticalConfigError: ReturnType<typeof createCriticalConfigErrorHandler>;
|
||||
appReadyRuntimeRunner: ReturnType<typeof createAppReadyRuntimeRunner>;
|
||||
};
|
||||
|
||||
export function composeAppReadyRuntime(options: AppReadyComposerOptions): AppReadyComposerResult {
|
||||
const reloadConfig = createReloadConfigHandler(
|
||||
createBuildReloadConfigMainDepsHandler(options.reloadConfigMainDeps)(),
|
||||
);
|
||||
const criticalConfigError = createCriticalConfigErrorHandler(
|
||||
createBuildCriticalConfigErrorMainDepsHandler(options.criticalConfigErrorMainDeps)(),
|
||||
);
|
||||
|
||||
const appReadyRuntimeRunner = createAppReadyRuntimeRunner(
|
||||
createBuildAppReadyRuntimeMainDepsHandler({
|
||||
...options.appReadyRuntimeMainDeps,
|
||||
reloadConfig,
|
||||
createImmersionTracker: createImmersionTrackerStartupHandler(
|
||||
createBuildImmersionTrackerStartupMainDepsHandler(
|
||||
options.immersionTrackerStartupMainDeps,
|
||||
)(),
|
||||
),
|
||||
onCriticalConfigErrors: criticalConfigError,
|
||||
})(),
|
||||
);
|
||||
|
||||
return {
|
||||
reloadConfig,
|
||||
criticalConfigError,
|
||||
appReadyRuntimeRunner,
|
||||
};
|
||||
}
|
||||
97
src/main/runtime/composers/ipc-runtime-composer.test.ts
Normal file
97
src/main/runtime/composers/ipc-runtime-composer.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { composeIpcRuntimeHandlers } from './ipc-runtime-composer';
|
||||
|
||||
test('composeIpcRuntimeHandlers returns callable IPC handlers and registration bridge', async () => {
|
||||
let registered = false;
|
||||
|
||||
const composed = composeIpcRuntimeHandlers<{ value: number }, { ok: boolean; received: number }>({
|
||||
mpvCommandMainDeps: {
|
||||
triggerSubsyncFromConfig: async () => {},
|
||||
openRuntimeOptionsPalette: () => {},
|
||||
cycleRuntimeOption: () => ({ ok: true }),
|
||||
showMpvOsd: () => {},
|
||||
replayCurrentSubtitle: () => {},
|
||||
playNextSubtitle: () => {},
|
||||
sendMpvCommand: () => {},
|
||||
isMpvConnected: () => false,
|
||||
hasRuntimeOptionsManager: () => true,
|
||||
},
|
||||
handleMpvCommandFromIpcRuntime: () => {},
|
||||
runSubsyncManualFromIpc: async (request) => ({ ok: true, received: request.value }),
|
||||
registration: {
|
||||
runtimeOptions: {
|
||||
getRuntimeOptionsManager: () => null,
|
||||
showMpvOsd: () => {},
|
||||
},
|
||||
mainDeps: {
|
||||
getInvisibleWindow: () => null,
|
||||
getMainWindow: () => null,
|
||||
getVisibleOverlayVisibility: () => false,
|
||||
getInvisibleOverlayVisibility: () => false,
|
||||
focusMainWindow: () => {},
|
||||
onOverlayModalClosed: () => {},
|
||||
openYomitanSettings: () => {},
|
||||
quitApp: () => {},
|
||||
toggleVisibleOverlay: () => {},
|
||||
tokenizeCurrentSubtitle: async () => null,
|
||||
getCurrentSubtitleRaw: () => '',
|
||||
getCurrentSubtitleAss: () => '',
|
||||
getMpvSubtitleRenderMetrics: () => ({}) as never,
|
||||
getSubtitlePosition: () => ({}) as never,
|
||||
getSubtitleStyle: () => ({}) as never,
|
||||
saveSubtitlePosition: () => {},
|
||||
getMecabTokenizer: () => null,
|
||||
getKeybindings: () => [],
|
||||
getConfiguredShortcuts: () => ({}) as never,
|
||||
getSecondarySubMode: () => 'hover' as never,
|
||||
getMpvClient: () => null,
|
||||
getAnkiConnectStatus: () => false,
|
||||
getRuntimeOptions: () => [],
|
||||
reportOverlayContentBounds: () => {},
|
||||
getAnilistStatus: () => ({}) as never,
|
||||
clearAnilistToken: () => {},
|
||||
openAnilistSetup: () => {},
|
||||
getAnilistQueueStatus: () => ({}) as never,
|
||||
retryAnilistQueueNow: async () => ({ ok: true, message: 'ok' }),
|
||||
appendClipboardVideoToQueue: () => ({ ok: true, message: 'ok' }),
|
||||
},
|
||||
ankiJimakuDeps: {
|
||||
patchAnkiConnectEnabled: () => {},
|
||||
getResolvedConfig: () => ({}) as never,
|
||||
getRuntimeOptionsManager: () => null,
|
||||
getSubtitleTimingTracker: () => null,
|
||||
getMpvClient: () => null,
|
||||
getAnkiIntegration: () => null,
|
||||
setAnkiIntegration: () => {},
|
||||
getKnownWordCacheStatePath: () => '',
|
||||
showDesktopNotification: () => {},
|
||||
createFieldGroupingCallback: () => (() => {}) as never,
|
||||
broadcastRuntimeOptionsChanged: () => {},
|
||||
getFieldGroupingResolver: () => null,
|
||||
setFieldGroupingResolver: () => {},
|
||||
parseMediaInfo: () => ({}) as never,
|
||||
getCurrentMediaPath: () => null,
|
||||
jimakuFetchJson: async () => ({ data: null }) as never,
|
||||
getJimakuMaxEntryResults: () => 0,
|
||||
getJimakuLanguagePreference: () => 'ja' as never,
|
||||
resolveJimakuApiKey: async () => null,
|
||||
isRemoteMediaPath: () => false,
|
||||
downloadToFile: async () => ({ ok: true, path: '/tmp/file' }),
|
||||
},
|
||||
registerIpcRuntimeServices: () => {
|
||||
registered = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(typeof composed.handleMpvCommandFromIpc, 'function');
|
||||
assert.equal(typeof composed.runSubsyncManualFromIpc, 'function');
|
||||
assert.equal(typeof composed.registerIpcRuntimeHandlers, 'function');
|
||||
|
||||
const result = await composed.runSubsyncManualFromIpc({ value: 7 });
|
||||
assert.deepEqual(result, { ok: true, received: 7 });
|
||||
|
||||
composed.registerIpcRuntimeHandlers();
|
||||
assert.equal(registered, true);
|
||||
});
|
||||
75
src/main/runtime/composers/ipc-runtime-composer.ts
Normal file
75
src/main/runtime/composers/ipc-runtime-composer.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { RegisterIpcRuntimeServicesParams } from '../../ipc-runtime';
|
||||
import {
|
||||
createBuildMpvCommandFromIpcRuntimeMainDepsHandler,
|
||||
createIpcRuntimeHandlers,
|
||||
} from '../domains/ipc';
|
||||
|
||||
type MpvCommand = (string | number)[];
|
||||
|
||||
type IpcMainDepsWithoutHandlers = Omit<
|
||||
RegisterIpcRuntimeServicesParams['mainDeps'],
|
||||
'handleMpvCommand' | 'runSubsyncManual'
|
||||
>;
|
||||
|
||||
type IpcRuntimeDeps<TRequest, TResult> = Parameters<
|
||||
typeof createIpcRuntimeHandlers<TRequest, TResult>
|
||||
>[0];
|
||||
|
||||
export type IpcRuntimeComposerOptions<TRequest, TResult> = {
|
||||
mpvCommandMainDeps: Parameters<typeof createBuildMpvCommandFromIpcRuntimeMainDepsHandler>[0];
|
||||
handleMpvCommandFromIpcRuntime: IpcRuntimeDeps<
|
||||
TRequest,
|
||||
TResult
|
||||
>['handleMpvCommandFromIpcDeps']['handleMpvCommandFromIpcRuntime'];
|
||||
runSubsyncManualFromIpc: (request: TRequest) => Promise<TResult>;
|
||||
registration: {
|
||||
runtimeOptions: RegisterIpcRuntimeServicesParams['runtimeOptions'];
|
||||
mainDeps: IpcMainDepsWithoutHandlers;
|
||||
ankiJimakuDeps: RegisterIpcRuntimeServicesParams['ankiJimakuDeps'];
|
||||
registerIpcRuntimeServices: (params: RegisterIpcRuntimeServicesParams) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export type IpcRuntimeComposerResult<TRequest, TResult> = {
|
||||
handleMpvCommandFromIpc: (command: MpvCommand) => void;
|
||||
runSubsyncManualFromIpc: (request: TRequest) => Promise<TResult>;
|
||||
registerIpcRuntimeHandlers: () => void;
|
||||
};
|
||||
|
||||
export function composeIpcRuntimeHandlers<TRequest, TResult>(
|
||||
options: IpcRuntimeComposerOptions<TRequest, TResult>,
|
||||
): IpcRuntimeComposerResult<TRequest, TResult> {
|
||||
const mpvCommandFromIpcRuntimeMainDeps = createBuildMpvCommandFromIpcRuntimeMainDepsHandler(
|
||||
options.mpvCommandMainDeps,
|
||||
)();
|
||||
const { handleMpvCommandFromIpc, runSubsyncManualFromIpc } = createIpcRuntimeHandlers<
|
||||
TRequest,
|
||||
TResult
|
||||
>({
|
||||
handleMpvCommandFromIpcDeps: {
|
||||
handleMpvCommandFromIpcRuntime: options.handleMpvCommandFromIpcRuntime,
|
||||
buildMpvCommandDeps: () => mpvCommandFromIpcRuntimeMainDeps,
|
||||
},
|
||||
runSubsyncManualFromIpcDeps: {
|
||||
runManualFromIpc: (request) => options.runSubsyncManualFromIpc(request),
|
||||
},
|
||||
});
|
||||
|
||||
const registerIpcRuntimeHandlers = (): void => {
|
||||
options.registration.registerIpcRuntimeServices({
|
||||
runtimeOptions: options.registration.runtimeOptions,
|
||||
mainDeps: {
|
||||
...options.registration.mainDeps,
|
||||
handleMpvCommand: (command) => handleMpvCommandFromIpc(command),
|
||||
runSubsyncManual: (request) => runSubsyncManualFromIpc(request as TRequest),
|
||||
},
|
||||
ankiJimakuDeps: options.registration.ankiJimakuDeps,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
handleMpvCommandFromIpc: (command) => handleMpvCommandFromIpc(command),
|
||||
runSubsyncManualFromIpc: (request) => runSubsyncManualFromIpc(request),
|
||||
registerIpcRuntimeHandlers,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { composeShortcutRuntimes } from './shortcuts-runtime-composer';
|
||||
|
||||
test('composeShortcutRuntimes returns callable shortcut runtime handlers', () => {
|
||||
const composed = composeShortcutRuntimes({
|
||||
globalShortcuts: {
|
||||
getConfiguredShortcutsMainDeps: {
|
||||
getResolvedConfig: () => ({}) as never,
|
||||
defaultConfig: {} as never,
|
||||
resolveConfiguredShortcuts: () => ({}) as never,
|
||||
},
|
||||
buildRegisterGlobalShortcutsMainDeps: () => ({
|
||||
getConfiguredShortcuts: () => ({}) as never,
|
||||
registerGlobalShortcutsCore: () => {},
|
||||
toggleVisibleOverlay: () => {},
|
||||
toggleInvisibleOverlay: () => {},
|
||||
openYomitanSettings: () => {},
|
||||
isDev: false,
|
||||
getMainWindow: () => null,
|
||||
}),
|
||||
buildRefreshGlobalAndOverlayShortcutsMainDeps: () => ({
|
||||
unregisterAllGlobalShortcuts: () => {},
|
||||
registerGlobalShortcuts: () => {},
|
||||
syncOverlayShortcuts: () => {},
|
||||
}),
|
||||
},
|
||||
numericShortcutRuntimeMainDeps: {
|
||||
globalShortcut: {
|
||||
register: () => true,
|
||||
unregister: () => {},
|
||||
},
|
||||
showMpvOsd: () => {},
|
||||
setTimer: (handler, timeoutMs) => setTimeout(handler, timeoutMs),
|
||||
clearTimer: (timer) => clearTimeout(timer),
|
||||
},
|
||||
numericSessions: {
|
||||
onMultiCopyDigit: () => {},
|
||||
onMineSentenceDigit: () => {},
|
||||
},
|
||||
overlayShortcutsRuntimeMainDeps: {
|
||||
overlayShortcutsRuntime: {
|
||||
registerOverlayShortcuts: () => {},
|
||||
unregisterOverlayShortcuts: () => {},
|
||||
syncOverlayShortcuts: () => {},
|
||||
refreshOverlayShortcuts: () => {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(typeof composed.getConfiguredShortcuts, 'function');
|
||||
assert.equal(typeof composed.registerGlobalShortcuts, 'function');
|
||||
assert.equal(typeof composed.refreshGlobalAndOverlayShortcuts, 'function');
|
||||
assert.equal(typeof composed.cancelPendingMultiCopy, 'function');
|
||||
assert.equal(typeof composed.startPendingMultiCopy, 'function');
|
||||
assert.equal(typeof composed.cancelPendingMineSentenceMultiple, 'function');
|
||||
assert.equal(typeof composed.startPendingMineSentenceMultiple, 'function');
|
||||
assert.equal(typeof composed.registerOverlayShortcuts, 'function');
|
||||
assert.equal(typeof composed.unregisterOverlayShortcuts, 'function');
|
||||
assert.equal(typeof composed.syncOverlayShortcuts, 'function');
|
||||
assert.equal(typeof composed.refreshOverlayShortcuts, 'function');
|
||||
});
|
||||
59
src/main/runtime/composers/shortcuts-runtime-composer.ts
Normal file
59
src/main/runtime/composers/shortcuts-runtime-composer.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createNumericShortcutRuntime } from '../../../core/services/numeric-shortcut';
|
||||
import {
|
||||
createBuildNumericShortcutRuntimeMainDepsHandler,
|
||||
createGlobalShortcutsRuntimeHandlers,
|
||||
createNumericShortcutSessionRuntimeHandlers,
|
||||
createOverlayShortcutsRuntimeHandlers,
|
||||
} from '../domains/shortcuts';
|
||||
|
||||
type GlobalShortcutsOptions = Parameters<typeof createGlobalShortcutsRuntimeHandlers>[0];
|
||||
type NumericShortcutRuntimeMainDeps = Parameters<
|
||||
typeof createBuildNumericShortcutRuntimeMainDepsHandler
|
||||
>[0];
|
||||
type NumericSessionOptions = Omit<
|
||||
Parameters<typeof createNumericShortcutSessionRuntimeHandlers>[0],
|
||||
'multiCopySession' | 'mineSentenceSession'
|
||||
>;
|
||||
type OverlayShortcutsMainDeps = Parameters<
|
||||
typeof createOverlayShortcutsRuntimeHandlers
|
||||
>[0]['overlayShortcutsRuntimeMainDeps'];
|
||||
|
||||
export type ShortcutsRuntimeComposerOptions = {
|
||||
globalShortcuts: GlobalShortcutsOptions;
|
||||
numericShortcutRuntimeMainDeps: NumericShortcutRuntimeMainDeps;
|
||||
numericSessions: NumericSessionOptions;
|
||||
overlayShortcutsRuntimeMainDeps: OverlayShortcutsMainDeps;
|
||||
};
|
||||
|
||||
export type ShortcutsRuntimeComposerResult = ReturnType<
|
||||
typeof createGlobalShortcutsRuntimeHandlers
|
||||
> &
|
||||
ReturnType<typeof createNumericShortcutSessionRuntimeHandlers> &
|
||||
ReturnType<typeof createOverlayShortcutsRuntimeHandlers>;
|
||||
|
||||
export function composeShortcutRuntimes(
|
||||
options: ShortcutsRuntimeComposerOptions,
|
||||
): ShortcutsRuntimeComposerResult {
|
||||
const globalShortcuts = createGlobalShortcutsRuntimeHandlers(options.globalShortcuts);
|
||||
|
||||
const numericShortcutRuntimeMainDeps = createBuildNumericShortcutRuntimeMainDepsHandler(
|
||||
options.numericShortcutRuntimeMainDeps,
|
||||
)();
|
||||
const numericShortcutRuntime = createNumericShortcutRuntime(numericShortcutRuntimeMainDeps);
|
||||
const numericSessions = createNumericShortcutSessionRuntimeHandlers({
|
||||
multiCopySession: numericShortcutRuntime.createSession(),
|
||||
mineSentenceSession: numericShortcutRuntime.createSession(),
|
||||
onMultiCopyDigit: options.numericSessions.onMultiCopyDigit,
|
||||
onMineSentenceDigit: options.numericSessions.onMineSentenceDigit,
|
||||
});
|
||||
|
||||
const overlayShortcuts = createOverlayShortcutsRuntimeHandlers({
|
||||
overlayShortcutsRuntimeMainDeps: options.overlayShortcutsRuntimeMainDeps,
|
||||
});
|
||||
|
||||
return {
|
||||
...globalShortcuts,
|
||||
...numericSessions,
|
||||
...overlayShortcuts,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { composeStartupLifecycleHandlers } from './startup-lifecycle-composer';
|
||||
|
||||
test('composeStartupLifecycleHandlers returns callable startup lifecycle handlers', () => {
|
||||
const composed = composeStartupLifecycleHandlers({
|
||||
registerProtocolUrlHandlersMainDeps: {
|
||||
registerOpenUrl: () => {},
|
||||
registerSecondInstance: () => {},
|
||||
handleAnilistSetupProtocolUrl: () => false,
|
||||
findAnilistSetupDeepLinkArgvUrl: () => null,
|
||||
logUnhandledOpenUrl: () => {},
|
||||
logUnhandledSecondInstanceUrl: () => {},
|
||||
},
|
||||
onWillQuitCleanupMainDeps: {
|
||||
destroyTray: () => {},
|
||||
stopConfigHotReload: () => {},
|
||||
restorePreviousSecondarySubVisibility: () => {},
|
||||
unregisterAllGlobalShortcuts: () => {},
|
||||
stopSubtitleWebsocket: () => {},
|
||||
stopTexthookerService: () => {},
|
||||
getYomitanParserWindow: () => null,
|
||||
clearYomitanParserState: () => {},
|
||||
getWindowTracker: () => null,
|
||||
getMpvSocket: () => null,
|
||||
getReconnectTimer: () => null,
|
||||
clearReconnectTimerRef: () => {},
|
||||
getSubtitleTimingTracker: () => null,
|
||||
getImmersionTracker: () => null,
|
||||
clearImmersionTracker: () => {},
|
||||
getAnkiIntegration: () => null,
|
||||
getAnilistSetupWindow: () => null,
|
||||
clearAnilistSetupWindow: () => {},
|
||||
getJellyfinSetupWindow: () => null,
|
||||
clearJellyfinSetupWindow: () => {},
|
||||
stopJellyfinRemoteSession: async () => {},
|
||||
},
|
||||
shouldRestoreWindowsOnActivateMainDeps: {
|
||||
isOverlayRuntimeInitialized: () => false,
|
||||
getAllWindowCount: () => 0,
|
||||
},
|
||||
restoreWindowsOnActivateMainDeps: {
|
||||
createMainWindow: () => {},
|
||||
createInvisibleWindow: () => {},
|
||||
updateVisibleOverlayVisibility: () => {},
|
||||
updateInvisibleOverlayVisibility: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(typeof composed.registerProtocolUrlHandlers, 'function');
|
||||
assert.equal(typeof composed.onWillQuitCleanup, 'function');
|
||||
assert.equal(typeof composed.shouldRestoreWindowsOnActivate, 'function');
|
||||
assert.equal(typeof composed.restoreWindowsOnActivate, 'function');
|
||||
});
|
||||
65
src/main/runtime/composers/startup-lifecycle-composer.ts
Normal file
65
src/main/runtime/composers/startup-lifecycle-composer.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
createOnWillQuitCleanupHandler,
|
||||
createRestoreWindowsOnActivateHandler,
|
||||
createShouldRestoreWindowsOnActivateHandler,
|
||||
} from '../app-lifecycle-actions';
|
||||
import { createBuildOnWillQuitCleanupDepsHandler } from '../app-lifecycle-main-cleanup';
|
||||
import {
|
||||
createBuildRestoreWindowsOnActivateMainDepsHandler,
|
||||
createBuildShouldRestoreWindowsOnActivateMainDepsHandler,
|
||||
} from '../app-lifecycle-main-activate';
|
||||
import { createBuildRegisterProtocolUrlHandlersMainDepsHandler } from '../protocol-url-handlers-main-deps';
|
||||
import { registerProtocolUrlHandlers } from '../protocol-url-handlers';
|
||||
|
||||
type RegisterProtocolUrlHandlersMainDeps = Parameters<
|
||||
typeof createBuildRegisterProtocolUrlHandlersMainDepsHandler
|
||||
>[0];
|
||||
type OnWillQuitCleanupDeps = Parameters<typeof createBuildOnWillQuitCleanupDepsHandler>[0];
|
||||
type ShouldRestoreWindowsOnActivateMainDeps = Parameters<
|
||||
typeof createBuildShouldRestoreWindowsOnActivateMainDepsHandler
|
||||
>[0];
|
||||
type RestoreWindowsOnActivateMainDeps = Parameters<
|
||||
typeof createBuildRestoreWindowsOnActivateMainDepsHandler
|
||||
>[0];
|
||||
|
||||
export type StartupLifecycleComposerOptions = {
|
||||
registerProtocolUrlHandlersMainDeps: RegisterProtocolUrlHandlersMainDeps;
|
||||
onWillQuitCleanupMainDeps: OnWillQuitCleanupDeps;
|
||||
shouldRestoreWindowsOnActivateMainDeps: ShouldRestoreWindowsOnActivateMainDeps;
|
||||
restoreWindowsOnActivateMainDeps: RestoreWindowsOnActivateMainDeps;
|
||||
};
|
||||
|
||||
export type StartupLifecycleComposerResult = {
|
||||
registerProtocolUrlHandlers: () => void;
|
||||
onWillQuitCleanup: () => void;
|
||||
shouldRestoreWindowsOnActivate: () => boolean;
|
||||
restoreWindowsOnActivate: () => void;
|
||||
};
|
||||
|
||||
export function composeStartupLifecycleHandlers(
|
||||
options: StartupLifecycleComposerOptions,
|
||||
): StartupLifecycleComposerResult {
|
||||
const registerProtocolUrlHandlersMainDeps = createBuildRegisterProtocolUrlHandlersMainDepsHandler(
|
||||
options.registerProtocolUrlHandlersMainDeps,
|
||||
)();
|
||||
|
||||
const onWillQuitCleanupHandler = createOnWillQuitCleanupHandler(
|
||||
createBuildOnWillQuitCleanupDepsHandler(options.onWillQuitCleanupMainDeps)(),
|
||||
);
|
||||
const shouldRestoreWindowsOnActivateHandler = createShouldRestoreWindowsOnActivateHandler(
|
||||
createBuildShouldRestoreWindowsOnActivateMainDepsHandler(
|
||||
options.shouldRestoreWindowsOnActivateMainDeps,
|
||||
)(),
|
||||
);
|
||||
const restoreWindowsOnActivateHandler = createRestoreWindowsOnActivateHandler(
|
||||
createBuildRestoreWindowsOnActivateMainDepsHandler(options.restoreWindowsOnActivateMainDeps)(),
|
||||
);
|
||||
|
||||
return {
|
||||
registerProtocolUrlHandlers: () =>
|
||||
registerProtocolUrlHandlers(registerProtocolUrlHandlersMainDeps),
|
||||
onWillQuitCleanup: () => onWillQuitCleanupHandler(),
|
||||
shouldRestoreWindowsOnActivate: () => shouldRestoreWindowsOnActivateHandler(),
|
||||
restoreWindowsOnActivate: () => restoreWindowsOnActivateHandler(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user