From 2b70b54faf94ebc5751144e1cc8aab5b8783e903 Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 20 Feb 2026 03:14:47 -0800 Subject: [PATCH] refactor: extract startup and initial args runtime wiring --- docs/subagents/INDEX.md | 2 +- .../codex-task85-20260219T233711Z-46hc.md | 5 + src/main.ts | 113 ++++++++---------- .../initial-args-runtime-handler.test.ts | 24 ++++ .../runtime/initial-args-runtime-handler.ts | 10 ++ .../runtime/startup-runtime-handlers.test.ts | 72 +++++++++++ src/main/runtime/startup-runtime-handlers.ts | 55 +++++++++ 7 files changed, 219 insertions(+), 62 deletions(-) create mode 100644 src/main/runtime/initial-args-runtime-handler.test.ts create mode 100644 src/main/runtime/initial-args-runtime-handler.ts create mode 100644 src/main/runtime/startup-runtime-handlers.test.ts create mode 100644 src/main/runtime/startup-runtime-handlers.ts diff --git a/docs/subagents/INDEX.md b/docs/subagents/INDEX.md index 00b2350..7091b8a 100644 --- a/docs/subagents/INDEX.md +++ b/docs/subagents/INDEX.md @@ -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-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-20T11:06:51Z` | +| `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-20T11:14:14Z` | | `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-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` | diff --git a/docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md b/docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md index c962123..87af7aa 100644 --- a/docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md +++ b/docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md @@ -9,6 +9,11 @@ ## Current Work (newest first) +- [2026-02-20T11:14:14Z] progress: extracted startup/app-lifecycle composition from `src/main.ts` into `src/main/runtime/startup-runtime-handlers.ts`; rewired `main.ts` to inject `createAppLifecycleRuntimeRunner` + `createStartupBootstrapRuntimeDeps` into `createStartupRuntimeHandlers(...)`. +- [2026-02-20T11:14:14Z] progress: extracted initial-args composition from `src/main.ts` into `src/main/runtime/initial-args-runtime-handler.ts`; `main.ts` now constructs `handleInitialArgsRuntimeHandler` with one runtime factory call. +- [2026-02-20T11:14:14Z] progress: added parity tests `src/main/runtime/startup-runtime-handlers.test.ts` and `src/main/runtime/initial-args-runtime-handler.test.ts` (fixing previous `node:sqlite` test-import leak by removing direct runtime imports from startup handler module). +- [2026-02-20T11:14:14Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + focused suites pass for `startup-runtime-handlers*`, `startup-lifecycle-main-deps*`, `startup-bootstrap-main-deps*`, `startup-bootstrap-deps-builder*`, `startup-warmups*`, `initial-args-runtime-handler*`, `initial-args-handler*`, `cli-command-context-factory*`, and `cli-command-prechecks*` (23/23). +- [2026-02-20T11:14:14Z] scope: staging `src/main.ts`, new startup/initial-args runtime handler modules + tests, and subagent bookkeeping only. - [2026-02-20T11:06:51Z] progress: extracted overlay bootstrap composition from `src/main.ts` into `src/main/runtime/overlay-runtime-bootstrap-handlers.ts`; `main.ts` now creates `initializeOverlayRuntime` through `createOverlayRuntimeBootstrapHandlers(...)`. - [2026-02-20T11:06:51Z] progress: added `src/main/runtime/overlay-runtime-bootstrap-handlers.test.ts` for composed bootstrap behavior. - [2026-02-20T11:06:51Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + focused suites pass for `overlay-runtime-bootstrap-handlers*`, `overlay-runtime-bootstrap*`, `overlay-runtime-options*`, `overlay-window-runtime-handlers*`, `tray-runtime-handlers*`, and `cli-command-context-factory*` (8/8). diff --git a/src/main.ts b/src/main.ts index 86e0dc4..2fe52e1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -243,8 +243,7 @@ import { createBuildStartJellyfinRemoteSessionMainDepsHandler, createBuildStopJellyfinRemoteSessionMainDepsHandler, } from './main/runtime/jellyfin-remote-session-main-deps'; -import { createHandleInitialArgsHandler } from './main/runtime/initial-args-handler'; -import { createBuildHandleInitialArgsMainDepsHandler } from './main/runtime/initial-args-main-deps'; +import { createInitialArgsRuntimeHandler } from './main/runtime/initial-args-runtime-handler'; import { createHandleTexthookerOnlyModeTransitionHandler } from './main/runtime/cli-command-prechecks'; import { createBuildHandleTexthookerOnlyModeTransitionMainDepsHandler } from './main/runtime/cli-command-prechecks-main-deps'; import { @@ -394,7 +393,6 @@ import { createBuildRestoreWindowsOnActivateMainDepsHandler, createBuildShouldRestoreWindowsOnActivateMainDepsHandler, } from './main/runtime/app-lifecycle-main-activate'; -import { createBuildStartupBootstrapRuntimeFactoryDepsHandler } from './main/runtime/startup-bootstrap-deps-builder'; import { buildRestartRequiredConfigMessage, createConfigHotReloadAppliedHandler, @@ -413,8 +411,7 @@ import { createBuildReloadConfigMainDepsHandler, } from './main/runtime/startup-config-main-deps'; import { createBuildAppReadyRuntimeMainDepsHandler } from './main/runtime/app-ready-main-deps'; -import { createBuildAppLifecycleRuntimeRunnerMainDepsHandler } from './main/runtime/startup-lifecycle-main-deps'; -import { createBuildStartupBootstrapMainDepsHandler } from './main/runtime/startup-bootstrap-main-deps'; +import { createStartupRuntimeHandlers } from './main/runtime/startup-runtime-handlers'; import { enforceUnsupportedWaylandMode, forceX11Backend, @@ -484,6 +481,8 @@ import { createAnilistUpdateQueue } from './core/services/anilist/anilist-update import { createJellyfinTokenStore } from './core/services/jellyfin-token-store'; import { applyRuntimeOptionResultRuntime } from './core/services/runtime-options-ipc'; import { createAppReadyRuntimeRunner } from './main/app-lifecycle'; +import { createStartupBootstrapRuntimeDeps } from './main/startup'; +import { createAppLifecycleRuntimeRunner } from './main/startup-lifecycle'; import { handleMpvCommandFromIpcRuntime } from './main/ipc-mpv-command'; import { registerIpcRuntimeServices } from './main/ipc-runtime'; import { createAnkiJimakuIpcRuntimeServiceDeps } from './main/dependencies'; @@ -500,13 +499,11 @@ import { } from './main/frequency-dictionary-runtime'; import { createMediaRuntimeService } from './main/media-runtime'; import { createOverlayVisibilityRuntimeService } from './main/overlay-visibility-runtime'; -import { type AppState, applyStartupState, createAppState } from './main/state'; +import { type AppState, type StartupState, applyStartupState, createAppState } from './main/state'; import { isAllowedAnilistExternalUrl, isAllowedAnilistSetupNavigationUrl, } from './main/anilist-url-guard'; -import { createStartupBootstrapRuntimeDeps } from './main/startup'; -import { createAppLifecycleRuntimeRunner } from './main/startup-lifecycle'; import { ConfigService, DEFAULT_CONFIG, @@ -2130,8 +2127,12 @@ const buildAppReadyRuntimeMainDepsHandler = createBuildAppReadyRuntimeMainDepsHa }); const appReadyRuntimeRunner = createAppReadyRuntimeRunner(buildAppReadyRuntimeMainDepsHandler()); -const buildAppLifecycleRuntimeRunnerMainDepsHandler = - createBuildAppLifecycleRuntimeRunnerMainDepsHandler({ +const { appLifecycleRuntimeRunner, runAndApplyStartupState } = createStartupRuntimeHandlers< + CliArgs, + StartupState, + ReturnType +>({ + appLifecycleRuntimeRunnerMainDeps: { app, platform: process.platform, shouldStartApp: (nextArgs: CliArgs) => shouldStartApp(nextArgs), @@ -2145,53 +2146,47 @@ const buildAppLifecycleRuntimeRunnerMainDepsHandler = shouldRestoreWindowsOnActivate: () => shouldRestoreWindowsOnActivateHandler(), restoreWindowsOnActivate: () => restoreWindowsOnActivateHandler(), shouldQuitOnWindowAllClosed: () => !appState.backgroundMode, - }); -const appLifecycleRuntimeRunnerMainDeps = buildAppLifecycleRuntimeRunnerMainDepsHandler(); -const appLifecycleRuntimeRunner = createAppLifecycleRuntimeRunner( - appLifecycleRuntimeRunnerMainDeps, -); + }, + createAppLifecycleRuntimeRunner: (params) => createAppLifecycleRuntimeRunner(params), + buildStartupBootstrapMainDeps: (startAppLifecycle) => ({ + argv: process.argv, + parseArgs: (argv: string[]) => parseArgs(argv), + setLogLevel: (level: string, source: LogLevelSource) => { + setLogLevel(level, source); + }, + forceX11Backend: (args: CliArgs) => { + forceX11Backend(args); + }, + enforceUnsupportedWaylandMode: (args: CliArgs) => { + enforceUnsupportedWaylandMode(args); + }, + shouldStartApp: (args: CliArgs) => shouldStartApp(args), + getDefaultSocketPath: () => getDefaultSocketPath(), + defaultTexthookerPort: DEFAULT_TEXTHOOKER_PORT, + configDir: CONFIG_DIR, + defaultConfig: DEFAULT_CONFIG, + generateConfigTemplate: (config: ResolvedConfig) => generateConfigTemplate(config), + generateDefaultConfigFile: ( + args: CliArgs, + options: { + configDir: string; + defaultConfig: unknown; + generateTemplate: (config: unknown) => string; + }, + ) => generateDefaultConfigFile(args, options), + setExitCode: (code) => { + process.exitCode = code; + }, + quitApp: () => app.quit(), + logGenerateConfigError: (message) => logger.error(message), + startAppLifecycle, + }), + createStartupBootstrapRuntimeDeps: (deps) => createStartupBootstrapRuntimeDeps(deps), + runStartupBootstrapRuntime, + applyStartupState: (startupState) => applyStartupState(appState, startupState), +}); -const buildStartupBootstrapMainDepsHandler = createBuildStartupBootstrapMainDepsHandler({ - argv: process.argv, - parseArgs: (argv: string[]) => parseArgs(argv), - setLogLevel: (level: string, source: LogLevelSource) => { - setLogLevel(level, source); - }, - forceX11Backend: (args: CliArgs) => { - forceX11Backend(args); - }, - enforceUnsupportedWaylandMode: (args: CliArgs) => { - enforceUnsupportedWaylandMode(args); - }, - shouldStartApp: (args: CliArgs) => shouldStartApp(args), - getDefaultSocketPath: () => getDefaultSocketPath(), - defaultTexthookerPort: DEFAULT_TEXTHOOKER_PORT, - configDir: CONFIG_DIR, - defaultConfig: DEFAULT_CONFIG, - generateConfigTemplate: (config: ResolvedConfig) => generateConfigTemplate(config), - generateDefaultConfigFile: ( - args: CliArgs, - options: { - configDir: string; - defaultConfig: unknown; - generateTemplate: (config: unknown) => string; - }, - ) => generateDefaultConfigFile(args, options), - setExitCode: (code) => { - process.exitCode = code; - }, - quitApp: () => app.quit(), - logGenerateConfigError: (message) => logger.error(message), - startAppLifecycle: appLifecycleRuntimeRunner, - }); -const buildStartupBootstrapRuntimeFactoryDepsHandler = - createBuildStartupBootstrapRuntimeFactoryDepsHandler(buildStartupBootstrapMainDepsHandler()); - -const startupState = runStartupBootstrapRuntime( - createStartupBootstrapRuntimeDeps(buildStartupBootstrapRuntimeFactoryDepsHandler()), -); - -applyStartupState(appState, startupState); +runAndApplyStartupState(); void refreshAnilistClientSecretState({ force: true }); anilistStateRuntime.refreshRetryQueueState(); @@ -2214,7 +2209,7 @@ function handleCliCommand(args: CliArgs, source: CliCommandSource = 'initial'): handleCliCommandRuntimeServiceWithContext(args, source, cliContext); } -const buildHandleInitialArgsMainDepsHandler = createBuildHandleInitialArgsMainDepsHandler({ +const handleInitialArgsRuntimeHandler = createInitialArgsRuntimeHandler({ getInitialArgs: () => appState.initialArgs, isBackgroundMode: () => appState.backgroundMode, ensureTray: () => ensureTray(), @@ -2224,10 +2219,6 @@ const buildHandleInitialArgsMainDepsHandler = createBuildHandleInitialArgsMainDe logInfo: (message) => logger.info(message), handleCliCommand: (args, source) => handleCliCommand(args, source), }); -const handleInitialArgsMainDeps = buildHandleInitialArgsMainDepsHandler(); -const handleInitialArgsRuntimeHandler = createHandleInitialArgsHandler( - handleInitialArgsMainDeps, -); function handleInitialArgs(): void { handleInitialArgsRuntimeHandler(); diff --git a/src/main/runtime/initial-args-runtime-handler.test.ts b/src/main/runtime/initial-args-runtime-handler.test.ts new file mode 100644 index 0000000..6b764c8 --- /dev/null +++ b/src/main/runtime/initial-args-runtime-handler.test.ts @@ -0,0 +1,24 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { createInitialArgsRuntimeHandler } from './initial-args-runtime-handler'; + +test('initial args runtime handler composes main deps and runs initial command flow', () => { + const calls: string[] = []; + const handleInitialArgs = createInitialArgsRuntimeHandler({ + getInitialArgs: () => ({ start: true } as never), + isBackgroundMode: () => true, + ensureTray: () => calls.push('tray'), + isTexthookerOnlyMode: () => false, + hasImmersionTracker: () => true, + getMpvClient: () => ({ + connected: false, + connect: () => calls.push('connect'), + }), + logInfo: (message) => calls.push(`log:${message}`), + handleCliCommand: (_args, source) => calls.push(`cli:${source}`), + }); + + handleInitialArgs(); + + assert.deepEqual(calls, ['tray', 'log:Auto-connecting MPV client for immersion tracking', 'connect', 'cli:initial']); +}); diff --git a/src/main/runtime/initial-args-runtime-handler.ts b/src/main/runtime/initial-args-runtime-handler.ts new file mode 100644 index 0000000..dc2da8a --- /dev/null +++ b/src/main/runtime/initial-args-runtime-handler.ts @@ -0,0 +1,10 @@ +import { createHandleInitialArgsHandler } from './initial-args-handler'; +import { createBuildHandleInitialArgsMainDepsHandler } from './initial-args-main-deps'; + +type InitialArgsMainDeps = Parameters[0]; + +export function createInitialArgsRuntimeHandler(deps: InitialArgsMainDeps) { + const buildHandleInitialArgsMainDepsHandler = createBuildHandleInitialArgsMainDepsHandler(deps); + const handleInitialArgsMainDeps = buildHandleInitialArgsMainDepsHandler(); + return createHandleInitialArgsHandler(handleInitialArgsMainDeps); +} diff --git a/src/main/runtime/startup-runtime-handlers.test.ts b/src/main/runtime/startup-runtime-handlers.test.ts new file mode 100644 index 0000000..5c9617a --- /dev/null +++ b/src/main/runtime/startup-runtime-handlers.test.ts @@ -0,0 +1,72 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import type { CliArgs } from '../../cli/args'; +import { createStartupRuntimeHandlers } from './startup-runtime-handlers'; + +test('startup runtime handlers compose lifecycle runner and startup bootstrap apply flow', () => { + const calls: string[] = []; + const appliedStates: Array<{ mode: string }> = []; + + const runtime = createStartupRuntimeHandlers< + { command: string }, + { mode: string }, + { startAppLifecycle: (args: CliArgs) => void } + >({ + appLifecycleRuntimeRunnerMainDeps: { + app: { on: () => {} } as never, + platform: 'darwin', + shouldStartApp: () => true, + parseArgs: () => ({}) as never, + handleCliCommand: () => calls.push('handle-cli'), + printHelp: () => calls.push('help'), + logNoRunningInstance: () => calls.push('no-instance'), + onReady: async () => { + calls.push('ready'); + }, + onWillQuitCleanup: () => calls.push('cleanup'), + shouldRestoreWindowsOnActivate: () => true, + restoreWindowsOnActivate: () => calls.push('restore'), + shouldQuitOnWindowAllClosed: () => false, + }, + createAppLifecycleRuntimeRunner: () => (args) => { + calls.push(`lifecycle:${args.command}`); + }, + buildStartupBootstrapMainDeps: (startAppLifecycle) => ({ + argv: ['node', 'main.js'], + parseArgs: () => ({ command: 'start' }) as never, + setLogLevel: () => {}, + forceX11Backend: () => {}, + enforceUnsupportedWaylandMode: () => {}, + shouldStartApp: () => true, + getDefaultSocketPath: () => '/tmp/mpv.sock', + defaultTexthookerPort: 5174, + configDir: '/tmp/config', + defaultConfig: {} as never, + generateConfigTemplate: () => 'template', + generateDefaultConfigFile: async () => 0, + setExitCode: () => {}, + quitApp: () => {}, + logGenerateConfigError: () => {}, + startAppLifecycle: (args) => { + const typedArgs = args as unknown as { command: string }; + calls.push(`start-lifecycle:${typedArgs.command}`); + startAppLifecycle(typedArgs); + }, + }), + createStartupBootstrapRuntimeDeps: (deps) => ({ + startAppLifecycle: deps.startAppLifecycle, + }), + runStartupBootstrapRuntime: (deps) => { + deps.startAppLifecycle({ command: 'start' } as never); + return { mode: 'started' }; + }, + applyStartupState: (state) => { + appliedStates.push(state); + }, + }); + + const state = runtime.runAndApplyStartupState(); + assert.deepEqual(state, { mode: 'started' }); + assert.deepEqual(appliedStates, [{ mode: 'started' }]); + assert.deepEqual(calls, ['start-lifecycle:start', 'lifecycle:start']); +}); diff --git a/src/main/runtime/startup-runtime-handlers.ts b/src/main/runtime/startup-runtime-handlers.ts new file mode 100644 index 0000000..7edf96f --- /dev/null +++ b/src/main/runtime/startup-runtime-handlers.ts @@ -0,0 +1,55 @@ +import { createBuildStartupBootstrapRuntimeFactoryDepsHandler } from './startup-bootstrap-deps-builder'; +import { createBuildAppLifecycleRuntimeRunnerMainDepsHandler } from './startup-lifecycle-main-deps'; +import { createBuildStartupBootstrapMainDepsHandler } from './startup-bootstrap-main-deps'; + +type AppLifecycleRuntimeRunnerMainDeps = Parameters< + typeof createBuildAppLifecycleRuntimeRunnerMainDepsHandler +>[0]; +type StartupBootstrapMainDeps = Parameters[0]; +type StartupBootstrapRuntimeFactoryDeps = Parameters< + typeof createBuildStartupBootstrapRuntimeFactoryDepsHandler +>[0]; + +export function createStartupRuntimeHandlers< + TCliArgs, + TStartupState, + TStartupBootstrapRuntimeDeps = unknown, +>(deps: { + appLifecycleRuntimeRunnerMainDeps: AppLifecycleRuntimeRunnerMainDeps; + createAppLifecycleRuntimeRunner: ( + params: AppLifecycleRuntimeRunnerMainDeps, + ) => (args: TCliArgs) => void; + buildStartupBootstrapMainDeps: ( + startAppLifecycle: (args: TCliArgs) => void, + ) => StartupBootstrapMainDeps; + createStartupBootstrapRuntimeDeps: ( + deps: StartupBootstrapRuntimeFactoryDeps, + ) => TStartupBootstrapRuntimeDeps; + runStartupBootstrapRuntime: (deps: TStartupBootstrapRuntimeDeps) => TStartupState; + applyStartupState: (startupState: TStartupState) => void; +}) { + const appLifecycleRuntimeRunnerMainDeps = + createBuildAppLifecycleRuntimeRunnerMainDepsHandler(deps.appLifecycleRuntimeRunnerMainDeps)(); + const appLifecycleRuntimeRunner = deps.createAppLifecycleRuntimeRunner( + appLifecycleRuntimeRunnerMainDeps, + ); + + const startupBootstrapMainDeps = createBuildStartupBootstrapMainDepsHandler( + deps.buildStartupBootstrapMainDeps(appLifecycleRuntimeRunner), + )(); + const startupBootstrapRuntimeFactoryDeps = + createBuildStartupBootstrapRuntimeFactoryDepsHandler(startupBootstrapMainDeps)(); + + const runAndApplyStartupState = (): TStartupState => { + const startupState = deps.runStartupBootstrapRuntime( + deps.createStartupBootstrapRuntimeDeps(startupBootstrapRuntimeFactoryDeps), + ); + deps.applyStartupState(startupState); + return startupState; + }; + + return { + appLifecycleRuntimeRunner, + runAndApplyStartupState, + }; +}