From e8db67e621d12ac6061cb1e443e216f5b2ee45ad Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 20 Feb 2026 02:14:40 -0800 Subject: [PATCH] refactor: extract tray runtime handler wiring --- docs/subagents/INDEX.md | 2 +- .../codex-task85-20260219T233711Z-46hc.md | 4 + src/main.ts | 48 ++++------ .../runtime/tray-runtime-handlers.test.ts | 96 +++++++++++++++++++ src/main/runtime/tray-runtime-handlers.ts | 48 ++++++++++ 5 files changed, 167 insertions(+), 31 deletions(-) create mode 100644 src/main/runtime/tray-runtime-handlers.test.ts create mode 100644 src/main/runtime/tray-runtime-handlers.ts diff --git a/docs/subagents/INDEX.md b/docs/subagents/INDEX.md index b3d17a3..b728b6c 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-20T10:06:15Z` | +| `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-20T10:14: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-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 3ee2702..5772098 100644 --- a/docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md +++ b/docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md @@ -9,6 +9,10 @@ ## Current Work (newest first) +- [2026-02-20T10:14:17Z] progress: extracted tray composition wiring from `src/main.ts` into `src/main/runtime/tray-runtime-handlers.ts`; `main.ts` now composes `resolveTrayIconPath`/`buildTrayMenu`/`ensureTray`/`destroyTray` through one factory. +- [2026-02-20T10:14:17Z] progress: added `src/main/runtime/tray-runtime-handlers.test.ts` covering composed tray handler behavior. +- [2026-02-20T10:14:17Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + focused suites pass for `tray-runtime-handlers*`, `tray-main-actions*`, `tray-main-deps*`, `tray-runtime*`, `tray-lifecycle*`, `cli-command-context-factory*`, and `overlay-visibility-runtime*` (12/12). +- [2026-02-20T10:14:17Z] scope: staging `src/main.ts`, new tray runtime handlers module/tests, and subagent bookkeeping only. - [2026-02-20T10:06:15Z] progress: extracted CLI command-context composition from `src/main.ts` into `src/main/runtime/cli-command-context-factory.ts`; `main.ts` now creates context via `createCliCommandContextFactory(...)`. - [2026-02-20T10:06:15Z] progress: added `src/main/runtime/cli-command-context-factory.test.ts` to validate composed factory behavior. - [2026-02-20T10:06:15Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + focused suites pass for `cli-command-context-factory*`, `cli-command-context*`, `ipc-runtime-handlers*`, `ipc-bridge-actions*`, `overlay-visibility-runtime*`, and `yomitan-extension-runtime*` (12/12). diff --git a/src/main.ts b/src/main.ts index d6ce557..92ff3e5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -289,7 +289,6 @@ import { createBuildUpdateVisibleOverlayBoundsMainDepsHandler, } from './main/runtime/overlay-window-layout-main-deps'; import { buildTrayMenuTemplateRuntime, resolveTrayIconPathRuntime } from './main/runtime/tray-runtime'; -import { createDestroyTrayHandler, createEnsureTrayHandler } from './main/runtime/tray-lifecycle'; import { createInitializeOverlayRuntimeHandler } from './main/runtime/overlay-runtime-bootstrap'; import { createOpenYomitanSettingsHandler } from './main/runtime/yomitan-settings-opener'; import { @@ -393,19 +392,10 @@ import { createBuildCreateOverlayWindowMainDepsHandler, } from './main/runtime/overlay-window-factory-main-deps'; import { - createBuildTrayMenuTemplateHandler, - createResolveTrayIconPathHandler, -} from './main/runtime/tray-main-actions'; -import { - createBuildResolveTrayIconPathMainDepsHandler, - createBuildTrayMenuTemplateMainDepsHandler, -} from './main/runtime/tray-main-deps'; -import { - createBuildDestroyTrayMainDepsHandler, - createBuildEnsureTrayMainDepsHandler, createBuildInitializeOverlayRuntimeBootstrapMainDepsHandler, createBuildOpenYomitanSettingsMainDepsHandler, } from './main/runtime/app-runtime-main-deps'; +import { createTrayRuntimeHandlers } from './main/runtime/tray-runtime-handlers'; import { createYomitanExtensionRuntime } from './main/runtime/yomitan-extension-runtime'; import { createBuildInitializeOverlayRuntimeOptionsHandler } from './main/runtime/overlay-runtime-options'; import { createBuildInitializeOverlayRuntimeMainDepsHandler } from './main/runtime/overlay-runtime-options-main-deps'; @@ -2525,7 +2515,7 @@ function resolveTrayIconPath(): string | null { } function buildTrayMenu(): Menu { - return Menu.buildFromTemplate(buildTrayMenuTemplateHandler()); + return buildTrayMenuHandler(); } function ensureTray(): void { @@ -3036,8 +3026,13 @@ const createInvisibleWindowHandler = createCreateInvisibleWindowHandler overlayManager.setInvisibleWindow(window), })(), ); -const resolveTrayIconPathHandler = createResolveTrayIconPathHandler( - createBuildResolveTrayIconPathMainDepsHandler({ +const { + resolveTrayIconPath: resolveTrayIconPathHandler, + buildTrayMenu: buildTrayMenuHandler, + ensureTray: ensureTrayHandler, + destroyTray: destroyTrayHandler, +} = createTrayRuntimeHandlers({ + resolveTrayIconPathDeps: { resolveTrayIconPathRuntime, platform: process.platform, resourcesPath: process.resourcesPath, @@ -3045,10 +3040,8 @@ const resolveTrayIconPathHandler = createResolveTrayIconPathHandler( dirname: __dirname, joinPath: (...parts) => path.join(...parts), fileExists: (candidate) => fs.existsSync(candidate), - })(), -); -const buildTrayMenuTemplateHandler = createBuildTrayMenuTemplateHandler( - createBuildTrayMenuTemplateMainDepsHandler({ + }, + buildTrayMenuTemplateDeps: { buildTrayMenuTemplateRuntime, initializeOverlayRuntime: () => initializeOverlayRuntime(), isOverlayRuntimeInitialized: () => appState.overlayRuntimeInitialized, @@ -3058,16 +3051,12 @@ const buildTrayMenuTemplateHandler = createBuildTrayMenuTemplateHandler( openJellyfinSetupWindow: () => openJellyfinSetupWindow(), openAnilistSetupWindow: () => openAnilistSetupWindow(), quitApp: () => app.quit(), - })(), -); -const ensureTrayHandler = createEnsureTrayHandler( - createBuildEnsureTrayMainDepsHandler({ + }, + ensureTrayDeps: { getTray: () => appTray, setTray: (tray) => { appTray = tray as Tray | null; }, - buildTrayMenu: () => buildTrayMenu(), - resolveTrayIconPath: () => resolveTrayIconPath(), createImageFromPath: (iconPath) => nativeImage.createFromPath(iconPath), createEmptyImage: () => nativeImage.createEmpty(), createTray: (icon) => new Tray(icon as never), @@ -3077,16 +3066,15 @@ const ensureTrayHandler = createEnsureTrayHandler( initializeOverlayRuntime: () => initializeOverlayRuntime(), isOverlayRuntimeInitialized: () => appState.overlayRuntimeInitialized, setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible), - })(), -); -const destroyTrayHandler = createDestroyTrayHandler( - createBuildDestroyTrayMainDepsHandler({ + }, + destroyTrayDeps: { getTray: () => appTray, setTray: (tray) => { appTray = tray as Tray | null; }, - })(), -); + }, + buildMenuFromTemplate: (template) => Menu.buildFromTemplate(template), +}); const yomitanExtensionRuntime = createYomitanExtensionRuntime({ loadYomitanExtensionCore, userDataPath: USER_DATA_PATH, diff --git a/src/main/runtime/tray-runtime-handlers.test.ts b/src/main/runtime/tray-runtime-handlers.test.ts new file mode 100644 index 0000000..75374b5 --- /dev/null +++ b/src/main/runtime/tray-runtime-handlers.test.ts @@ -0,0 +1,96 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { createTrayRuntimeHandlers } from './tray-runtime-handlers'; + +test('tray runtime handlers compose resolve/menu/ensure/destroy handlers', () => { + let tray: { destroyed: boolean } | null = null; + let overlayInitialized = false; + let visibleOverlay = false; + const calls: string[] = []; + + const runtime = createTrayRuntimeHandlers({ + resolveTrayIconPathDeps: { + resolveTrayIconPathRuntime: () => '/tmp/SubMiner.png', + platform: 'darwin', + resourcesPath: '/resources', + appPath: '/app', + dirname: '/dirname', + joinPath: (...parts) => parts.join('/'), + fileExists: () => true, + }, + buildTrayMenuTemplateDeps: { + buildTrayMenuTemplateRuntime: () => [{ label: 'Open Overlay' }], + initializeOverlayRuntime: () => { + overlayInitialized = true; + }, + isOverlayRuntimeInitialized: () => overlayInitialized, + setVisibleOverlayVisible: (visible) => { + visibleOverlay = visible; + }, + openYomitanSettings: () => {}, + openRuntimeOptionsPalette: () => {}, + openJellyfinSetupWindow: () => {}, + openAnilistSetupWindow: () => {}, + quitApp: () => {}, + }, + ensureTrayDeps: { + getTray: () => tray as never, + setTray: (next) => { + tray = next as { destroyed: boolean } | null; + }, + createImageFromPath: () => ({ + isEmpty: () => false, + resize: () => ({ + isEmpty: () => false, + resize: () => ({} as never), + setTemplateImage: () => {}, + }), + setTemplateImage: () => {}, + }), + createEmptyImage: () => ({ + isEmpty: () => true, + resize: () => ({} as never), + setTemplateImage: () => {}, + }), + createTray: () => ({ + setContextMenu: () => calls.push('setContextMenu'), + setToolTip: () => calls.push('setToolTip'), + on: (_event: 'click', handler: () => void) => { + calls.push('on-click'); + handler(); + }, + destroy: () => { + calls.push('destroy'); + if (tray) tray.destroyed = true; + }, + }), + trayTooltip: 'SubMiner', + platform: 'darwin', + logWarn: (message) => calls.push(`warn:${message}`), + initializeOverlayRuntime: () => { + overlayInitialized = true; + }, + isOverlayRuntimeInitialized: () => overlayInitialized, + setVisibleOverlayVisible: (visible) => { + visibleOverlay = visible; + }, + }, + destroyTrayDeps: { + getTray: () => tray as never, + setTray: (next) => { + tray = next as { destroyed: boolean } | null; + }, + }, + buildMenuFromTemplate: (template) => ({ template }), + }); + + assert.equal(runtime.resolveTrayIconPath(), '/tmp/SubMiner.png'); + assert.deepEqual(runtime.buildTrayMenu(), { template: [{ label: 'Open Overlay' }] }); + runtime.ensureTray(); + assert.equal(overlayInitialized, true); + assert.equal(visibleOverlay, true); + assert.ok(tray); + runtime.destroyTray(); + assert.equal(tray, null); + assert.deepEqual(calls, ['setToolTip', 'setContextMenu', 'on-click', 'destroy']); +}); diff --git a/src/main/runtime/tray-runtime-handlers.ts b/src/main/runtime/tray-runtime-handlers.ts new file mode 100644 index 0000000..622da67 --- /dev/null +++ b/src/main/runtime/tray-runtime-handlers.ts @@ -0,0 +1,48 @@ +import { createDestroyTrayHandler, createEnsureTrayHandler } from './tray-lifecycle'; +import { createBuildDestroyTrayMainDepsHandler, createBuildEnsureTrayMainDepsHandler } from './app-runtime-main-deps'; +import { createBuildTrayMenuTemplateHandler, createResolveTrayIconPathHandler } from './tray-main-actions'; +import { + createBuildResolveTrayIconPathMainDepsHandler, + createBuildTrayMenuTemplateMainDepsHandler, +} from './tray-main-deps'; + +type ResolveTrayIconPathMainDeps = Parameters[0]; +type BuildTrayMenuTemplateMainDeps = Parameters< + typeof createBuildTrayMenuTemplateMainDepsHandler +>[0]; +type EnsureTrayMainDeps = Parameters[0]; +type DestroyTrayMainDeps = Parameters[0]; + +export function createTrayRuntimeHandlers(deps: { + resolveTrayIconPathDeps: ResolveTrayIconPathMainDeps; + buildTrayMenuTemplateDeps: BuildTrayMenuTemplateMainDeps; + ensureTrayDeps: Omit; + destroyTrayDeps: DestroyTrayMainDeps; + buildMenuFromTemplate: (template: TMenuItem[]) => TMenu; +}) { + const resolveTrayIconPath = createResolveTrayIconPathHandler( + createBuildResolveTrayIconPathMainDepsHandler(deps.resolveTrayIconPathDeps)(), + ); + const buildTrayMenuTemplate = createBuildTrayMenuTemplateHandler( + createBuildTrayMenuTemplateMainDepsHandler(deps.buildTrayMenuTemplateDeps)(), + ); + const buildTrayMenu = () => deps.buildMenuFromTemplate(buildTrayMenuTemplate()); + + const ensureTray = createEnsureTrayHandler( + createBuildEnsureTrayMainDepsHandler({ + ...deps.ensureTrayDeps, + buildTrayMenu: () => buildTrayMenu(), + resolveTrayIconPath: () => resolveTrayIconPath(), + })(), + ); + const destroyTray = createDestroyTrayHandler( + createBuildDestroyTrayMainDepsHandler(deps.destroyTrayDeps)(), + ); + + return { + resolveTrayIconPath, + buildTrayMenu, + ensureTray, + destroyTray, + }; +}