refactor: extract numeric and overlay shortcuts runtime wiring

This commit is contained in:
2026-02-20 03:35:47 -08:00
parent eef8a7eb41
commit 12c5d956bc
7 changed files with 203 additions and 138 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-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:32:45Z` |
| `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:35:21Z` |
| `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` |

View File

@@ -9,6 +9,11 @@
## Current Work (newest first)
- [2026-02-20T11:35:21Z] progress: extracted numeric shortcut session composition from `src/main.ts` into `src/main/runtime/numeric-shortcut-session-runtime-handlers.ts`; `main.ts` now gets `cancel/start` handlers for multi-copy and mine-sentence sessions from one runtime factory.
- [2026-02-20T11:35:21Z] progress: extracted overlay shortcuts lifecycle composition from `src/main.ts` into `src/main/runtime/overlay-shortcuts-runtime-handlers.ts`; `main.ts` now gets register/unregister/sync/refresh handlers via one runtime factory.
- [2026-02-20T11:35:21Z] progress: added parity tests `src/main/runtime/numeric-shortcut-session-runtime-handlers.test.ts` and `src/main/runtime/overlay-shortcuts-runtime-handlers.test.ts`.
- [2026-02-20T11:35:21Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + focused suites pass for `numeric-shortcut-session-runtime-handlers*`, `overlay-shortcuts-runtime-handlers*`, `numeric-shortcut-session-*`, and `overlay-shortcuts-lifecycle-*` (10/10).
- [2026-02-20T11:35:21Z] scope: staging `src/main.ts`, new numeric/overlay-shortcuts runtime handler modules/tests, and subagent bookkeeping only.
- [2026-02-20T11:32:45Z] progress: extracted MPV OSD composition from `src/main.ts` into `src/main/runtime/mpv-osd-runtime-handlers.ts`; `main.ts` now receives `appendToMpvLog` and `showMpvOsd` from one runtime factory.
- [2026-02-20T11:32:45Z] progress: extracted secondary subtitle mode composition from `src/main.ts` into `src/main/runtime/secondary-sub-mode-runtime-handler.ts`; `main.ts` now builds `cycleSecondarySubMode` via one runtime handler factory.
- [2026-02-20T11:32:45Z] progress: added parity tests `src/main/runtime/mpv-osd-runtime-handlers.test.ts` and `src/main/runtime/secondary-sub-mode-runtime-handler.test.ts`.

View File

@@ -290,27 +290,9 @@ import { buildTrayMenuTemplateRuntime, resolveTrayIconPathRuntime } from './main
import { createGlobalShortcutsRuntimeHandlers } from './main/runtime/global-shortcuts-runtime-handlers';
import { createMpvOsdRuntimeHandlers } from './main/runtime/mpv-osd-runtime-handlers';
import { createCycleSecondarySubModeRuntimeHandler } from './main/runtime/secondary-sub-mode-runtime-handler';
import {
createCancelNumericShortcutSessionHandler,
createStartNumericShortcutSessionHandler,
} from './main/runtime/numeric-shortcut-session-handlers';
import {
createBuildCancelNumericShortcutSessionMainDepsHandler,
createBuildStartNumericShortcutSessionMainDepsHandler,
} from './main/runtime/numeric-shortcut-session-main-deps';
import { createNumericShortcutSessionRuntimeHandlers } from './main/runtime/numeric-shortcut-session-runtime-handlers';
import { createBuildNumericShortcutRuntimeMainDepsHandler } from './main/runtime/numeric-shortcut-runtime-main-deps';
import {
createRefreshOverlayShortcutsHandler,
createRegisterOverlayShortcutsHandler,
createSyncOverlayShortcutsHandler,
createUnregisterOverlayShortcutsHandler,
} from './main/runtime/overlay-shortcuts-lifecycle';
import {
createBuildRefreshOverlayShortcutsMainDepsHandler,
createBuildRegisterOverlayShortcutsMainDepsHandler,
createBuildSyncOverlayShortcutsMainDepsHandler,
createBuildUnregisterOverlayShortcutsMainDepsHandler,
} from './main/runtime/overlay-shortcuts-lifecycle-main-deps';
import { createOverlayShortcutsRuntimeHandlers } from './main/runtime/overlay-shortcuts-runtime-handlers';
import { createBuildOverlayShortcutsRuntimeMainDepsHandler } from './main/runtime/overlay-shortcuts-runtime-main-deps';
import {
createMarkLastCardAsAudioCardHandler,
@@ -2565,108 +2547,32 @@ const numericShortcutRuntime = createNumericShortcutRuntime(
);
const multiCopySession = numericShortcutRuntime.createSession();
const mineSentenceSession = numericShortcutRuntime.createSession();
const buildCancelPendingMultiCopyMainDepsHandler =
createBuildCancelNumericShortcutSessionMainDepsHandler({
session: multiCopySession,
const {
cancelPendingMultiCopy,
startPendingMultiCopy,
cancelPendingMineSentenceMultiple,
startPendingMineSentenceMultiple,
} = createNumericShortcutSessionRuntimeHandlers({
multiCopySession,
mineSentenceSession,
onMultiCopyDigit: (count) => handleMultiCopyDigit(count),
onMineSentenceDigit: (count) => handleMineSentenceDigit(count),
});
const cancelPendingMultiCopyMainDeps =
buildCancelPendingMultiCopyMainDepsHandler();
const cancelPendingMultiCopyHandler = createCancelNumericShortcutSessionHandler(
cancelPendingMultiCopyMainDeps,
);
const buildStartPendingMultiCopyMainDepsHandler =
createBuildStartNumericShortcutSessionMainDepsHandler({
session: multiCopySession,
onDigit: (count) => handleMultiCopyDigit(count),
messages: {
prompt: 'Copy how many lines? Press 1-9 (Esc to cancel)',
timeout: 'Copy timeout',
cancelled: 'Cancelled',
const {
registerOverlayShortcuts,
unregisterOverlayShortcuts,
syncOverlayShortcuts,
refreshOverlayShortcuts,
} = createOverlayShortcutsRuntimeHandlers({
overlayShortcutsRuntimeMainDeps: {
overlayShortcutsRuntime,
},
});
const startPendingMultiCopyMainDeps =
buildStartPendingMultiCopyMainDepsHandler();
const startPendingMultiCopyHandler = createStartNumericShortcutSessionHandler(
startPendingMultiCopyMainDeps,
);
const buildCancelPendingMineSentenceMultipleMainDepsHandler =
createBuildCancelNumericShortcutSessionMainDepsHandler({
session: mineSentenceSession,
});
const cancelPendingMineSentenceMultipleMainDeps =
buildCancelPendingMineSentenceMultipleMainDepsHandler();
const cancelPendingMineSentenceMultipleHandler = createCancelNumericShortcutSessionHandler(
cancelPendingMineSentenceMultipleMainDeps,
);
const buildStartPendingMineSentenceMultipleMainDepsHandler =
createBuildStartNumericShortcutSessionMainDepsHandler({
session: mineSentenceSession,
onDigit: (count) => handleMineSentenceDigit(count),
messages: {
prompt: 'Mine how many lines? Press 1-9 (Esc to cancel)',
timeout: 'Mine sentence timeout',
cancelled: 'Cancelled',
},
});
const startPendingMineSentenceMultipleMainDeps =
buildStartPendingMineSentenceMultipleMainDepsHandler();
const startPendingMineSentenceMultipleHandler = createStartNumericShortcutSessionHandler(
startPendingMineSentenceMultipleMainDeps,
);
const buildRegisterOverlayShortcutsMainDepsHandler =
createBuildRegisterOverlayShortcutsMainDepsHandler({
overlayShortcutsRuntime,
});
const registerOverlayShortcutsMainDeps =
buildRegisterOverlayShortcutsMainDepsHandler();
const registerOverlayShortcutsHandler = createRegisterOverlayShortcutsHandler(
registerOverlayShortcutsMainDeps,
);
const buildUnregisterOverlayShortcutsMainDepsHandler =
createBuildUnregisterOverlayShortcutsMainDepsHandler({
overlayShortcutsRuntime,
});
const unregisterOverlayShortcutsMainDeps =
buildUnregisterOverlayShortcutsMainDepsHandler();
const unregisterOverlayShortcutsHandler = createUnregisterOverlayShortcutsHandler(
unregisterOverlayShortcutsMainDeps,
);
const buildSyncOverlayShortcutsMainDepsHandler = createBuildSyncOverlayShortcutsMainDepsHandler({
overlayShortcutsRuntime,
});
const syncOverlayShortcutsMainDeps = buildSyncOverlayShortcutsMainDepsHandler();
const syncOverlayShortcutsHandler = createSyncOverlayShortcutsHandler(
syncOverlayShortcutsMainDeps,
);
const buildRefreshOverlayShortcutsMainDepsHandler =
createBuildRefreshOverlayShortcutsMainDepsHandler({
overlayShortcutsRuntime,
});
const refreshOverlayShortcutsMainDeps =
buildRefreshOverlayShortcutsMainDepsHandler();
const refreshOverlayShortcutsHandler = createRefreshOverlayShortcutsHandler(
refreshOverlayShortcutsMainDeps,
);
async function triggerSubsyncFromConfig(): Promise<void> {
await subsyncRuntime.triggerFromConfig();
}
function cancelPendingMultiCopy(): void {
cancelPendingMultiCopyHandler();
}
function startPendingMultiCopy(timeoutMs: number): void {
startPendingMultiCopyHandler(timeoutMs);
}
function handleMultiCopyDigit(count: number): void {
handleMultiCopyDigitHandler(count);
}
@@ -3089,33 +2995,10 @@ async function mineSentenceCard(): Promise<void> {
await mineSentenceCardHandler();
}
function cancelPendingMineSentenceMultiple(): void {
cancelPendingMineSentenceMultipleHandler();
}
function startPendingMineSentenceMultiple(timeoutMs: number): void {
startPendingMineSentenceMultipleHandler(timeoutMs);
}
function handleMineSentenceDigit(count: number): void {
handleMineSentenceDigitHandler(count);
}
function registerOverlayShortcuts(): void {
registerOverlayShortcutsHandler();
}
function unregisterOverlayShortcuts(): void {
unregisterOverlayShortcutsHandler();
}
function syncOverlayShortcuts(): void {
syncOverlayShortcutsHandler();
}
function refreshOverlayShortcuts(): void {
refreshOverlayShortcutsHandler();
}
function setVisibleOverlayVisible(visible: boolean): void {
setVisibleOverlayVisibleHandler(visible);
}

View File

@@ -0,0 +1,35 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createNumericShortcutSessionRuntimeHandlers } from './numeric-shortcut-session-runtime-handlers';
test('numeric shortcut session runtime handlers compose cancel/start handlers', () => {
const calls: string[] = [];
const createSession = (name: string) => ({
start: ({ timeoutMs, onDigit }: { timeoutMs: number; onDigit: (digit: number) => void }) => {
calls.push(`${name}:start:${timeoutMs}`);
onDigit(3);
},
cancel: () => calls.push(`${name}:cancel`),
});
const runtime = createNumericShortcutSessionRuntimeHandlers({
multiCopySession: createSession('multi-copy'),
mineSentenceSession: createSession('mine-sentence'),
onMultiCopyDigit: (count) => calls.push(`multi-copy:digit:${count}`),
onMineSentenceDigit: (count) => calls.push(`mine-sentence:digit:${count}`),
});
runtime.cancelPendingMultiCopy();
runtime.startPendingMultiCopy(500);
runtime.cancelPendingMineSentenceMultiple();
runtime.startPendingMineSentenceMultiple(700);
assert.deepEqual(calls, [
'multi-copy:cancel',
'multi-copy:start:500',
'multi-copy:digit:3',
'mine-sentence:cancel',
'mine-sentence:start:700',
'mine-sentence:digit:3',
]);
});

View File

@@ -0,0 +1,67 @@
import {
createCancelNumericShortcutSessionHandler,
createStartNumericShortcutSessionHandler,
} from './numeric-shortcut-session-handlers';
import {
createBuildCancelNumericShortcutSessionMainDepsHandler,
createBuildStartNumericShortcutSessionMainDepsHandler,
} from './numeric-shortcut-session-main-deps';
type CancelNumericShortcutSessionMainDeps = Parameters<
typeof createBuildCancelNumericShortcutSessionMainDepsHandler
>[0];
export function createNumericShortcutSessionRuntimeHandlers(deps: {
multiCopySession: CancelNumericShortcutSessionMainDeps['session'];
mineSentenceSession: CancelNumericShortcutSessionMainDeps['session'];
onMultiCopyDigit: (count: number) => void;
onMineSentenceDigit: (count: number) => void;
}) {
const cancelPendingMultiCopyMainDeps = createBuildCancelNumericShortcutSessionMainDepsHandler({
session: deps.multiCopySession,
})();
const cancelPendingMultiCopyHandler =
createCancelNumericShortcutSessionHandler(cancelPendingMultiCopyMainDeps);
const startPendingMultiCopyMainDeps = createBuildStartNumericShortcutSessionMainDepsHandler({
session: deps.multiCopySession,
onDigit: deps.onMultiCopyDigit,
messages: {
prompt: 'Copy how many lines? Press 1-9 (Esc to cancel)',
timeout: 'Copy timeout',
cancelled: 'Cancelled',
},
})();
const startPendingMultiCopyHandler =
createStartNumericShortcutSessionHandler(startPendingMultiCopyMainDeps);
const cancelPendingMineSentenceMultipleMainDeps =
createBuildCancelNumericShortcutSessionMainDepsHandler({
session: deps.mineSentenceSession,
})();
const cancelPendingMineSentenceMultipleHandler = createCancelNumericShortcutSessionHandler(
cancelPendingMineSentenceMultipleMainDeps,
);
const startPendingMineSentenceMultipleMainDeps =
createBuildStartNumericShortcutSessionMainDepsHandler({
session: deps.mineSentenceSession,
onDigit: deps.onMineSentenceDigit,
messages: {
prompt: 'Mine how many lines? Press 1-9 (Esc to cancel)',
timeout: 'Mine sentence timeout',
cancelled: 'Cancelled',
},
})();
const startPendingMineSentenceMultipleHandler = createStartNumericShortcutSessionHandler(
startPendingMineSentenceMultipleMainDeps,
);
return {
cancelPendingMultiCopy: () => cancelPendingMultiCopyHandler(),
startPendingMultiCopy: (timeoutMs: number) => startPendingMultiCopyHandler(timeoutMs),
cancelPendingMineSentenceMultiple: () => cancelPendingMineSentenceMultipleHandler(),
startPendingMineSentenceMultiple: (timeoutMs: number) =>
startPendingMineSentenceMultipleHandler(timeoutMs),
};
}

View File

@@ -0,0 +1,24 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createOverlayShortcutsRuntimeHandlers } from './overlay-shortcuts-runtime-handlers';
test('overlay shortcuts runtime handlers compose lifecycle handlers', () => {
const calls: string[] = [];
const runtime = createOverlayShortcutsRuntimeHandlers({
overlayShortcutsRuntimeMainDeps: {
overlayShortcutsRuntime: {
registerOverlayShortcuts: () => calls.push('register'),
unregisterOverlayShortcuts: () => calls.push('unregister'),
syncOverlayShortcuts: () => calls.push('sync'),
refreshOverlayShortcuts: () => calls.push('refresh'),
},
},
});
runtime.registerOverlayShortcuts();
runtime.unregisterOverlayShortcuts();
runtime.syncOverlayShortcuts();
runtime.refreshOverlayShortcuts();
assert.deepEqual(calls, ['register', 'unregister', 'sync', 'refresh']);
});

View File

@@ -0,0 +1,51 @@
import {
createRefreshOverlayShortcutsHandler,
createRegisterOverlayShortcutsHandler,
createSyncOverlayShortcutsHandler,
createUnregisterOverlayShortcutsHandler,
} from './overlay-shortcuts-lifecycle';
import {
createBuildRefreshOverlayShortcutsMainDepsHandler,
createBuildRegisterOverlayShortcutsMainDepsHandler,
createBuildSyncOverlayShortcutsMainDepsHandler,
createBuildUnregisterOverlayShortcutsMainDepsHandler,
} from './overlay-shortcuts-lifecycle-main-deps';
type RegisterOverlayShortcutsMainDeps = Parameters<
typeof createBuildRegisterOverlayShortcutsMainDepsHandler
>[0];
export function createOverlayShortcutsRuntimeHandlers(deps: {
overlayShortcutsRuntimeMainDeps: RegisterOverlayShortcutsMainDeps;
}) {
const registerOverlayShortcutsMainDeps = createBuildRegisterOverlayShortcutsMainDepsHandler(
deps.overlayShortcutsRuntimeMainDeps,
)();
const registerOverlayShortcutsHandler =
createRegisterOverlayShortcutsHandler(registerOverlayShortcutsMainDeps);
const unregisterOverlayShortcutsMainDeps =
createBuildUnregisterOverlayShortcutsMainDepsHandler(
deps.overlayShortcutsRuntimeMainDeps,
)();
const unregisterOverlayShortcutsHandler =
createUnregisterOverlayShortcutsHandler(unregisterOverlayShortcutsMainDeps);
const syncOverlayShortcutsMainDeps = createBuildSyncOverlayShortcutsMainDepsHandler(
deps.overlayShortcutsRuntimeMainDeps,
)();
const syncOverlayShortcutsHandler = createSyncOverlayShortcutsHandler(syncOverlayShortcutsMainDeps);
const refreshOverlayShortcutsMainDeps = createBuildRefreshOverlayShortcutsMainDepsHandler(
deps.overlayShortcutsRuntimeMainDeps,
)();
const refreshOverlayShortcutsHandler =
createRefreshOverlayShortcutsHandler(refreshOverlayShortcutsMainDeps);
return {
registerOverlayShortcuts: () => registerOverlayShortcutsHandler(),
unregisterOverlayShortcuts: () => unregisterOverlayShortcutsHandler(),
syncOverlayShortcuts: () => syncOverlayShortcutsHandler(),
refreshOverlayShortcuts: () => refreshOverlayShortcutsHandler(),
};
}