mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-02 06:22:42 -08:00
refactor: simplify config and anki integration composition
This commit is contained in:
@@ -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-20T11:35:21Z` |
|
| `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:42:39Z` |
|
||||||
| `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` |
|
||||||
@@ -23,3 +23,4 @@ Read first. Keep concise.
|
|||||||
| `codex-jellyfin-secret-store-20260220T101428Z-om4z` | `codex-jellyfin-secret-store` | `Verify whether Jellyfin token can use same secret-store path as AniList token` | `completed` | `docs/subagents/agents/codex-jellyfin-secret-store-20260220T101428Z-om4z.md` | `2026-02-20T10:22:45Z` |
|
| `codex-jellyfin-secret-store-20260220T101428Z-om4z` | `codex-jellyfin-secret-store` | `Verify whether Jellyfin token can use same secret-store path as AniList token` | `completed` | `docs/subagents/agents/codex-jellyfin-secret-store-20260220T101428Z-om4z.md` | `2026-02-20T10:22:45Z` |
|
||||||
| `codex-vitepress-subagents-ignore-20260220T101755Z-k2m9` | `codex-vitepress-subagents-ignore` | `Exclude docs/subagents from VitePress build` | `completed` | `docs/subagents/agents/codex-vitepress-subagents-ignore-20260220T101755Z-k2m9.md` | `2026-02-20T10:18:30Z` |
|
| `codex-vitepress-subagents-ignore-20260220T101755Z-k2m9` | `codex-vitepress-subagents-ignore` | `Exclude docs/subagents from VitePress build` | `completed` | `docs/subagents/agents/codex-vitepress-subagents-ignore-20260220T101755Z-k2m9.md` | `2026-02-20T10:18:30Z` |
|
||||||
| `codex-preserve-linebreak-display-20260220T110436Z-r8f1` | `codex-preserve-linebreak-display` | `Fix visible overlay display artifact when subtitleStyle.preserveLineBreaks is disabled` | `completed` | `docs/subagents/agents/codex-preserve-linebreak-display-20260220T110436Z-r8f1.md` | `2026-02-20T11:10:51Z` |
|
| `codex-preserve-linebreak-display-20260220T110436Z-r8f1` | `codex-preserve-linebreak-display` | `Fix visible overlay display artifact when subtitleStyle.preserveLineBreaks is disabled` | `completed` | `docs/subagents/agents/codex-preserve-linebreak-display-20260220T110436Z-r8f1.md` | `2026-02-20T11:10:51Z` |
|
||||||
|
| `codex-review-refactor-cleanup-20260220T113818Z-i2ov` | `codex-review-refactor-cleanup` | `Review recent TASK-85 refactor effort and identify remaining cleanup work` | `handoff` | `docs/subagents/agents/codex-review-refactor-cleanup-20260220T113818Z-i2ov.md` | `2026-02-20T11:40:15Z` |
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
## Current Work (newest first)
|
## Current Work (newest first)
|
||||||
|
|
||||||
|
- [2026-02-20T11:42:39Z] progress: pivoted from `src/main.ts` micro-extractions to targeted large-file ROI on `/src/config/service.ts` and `/src/anki-integration.ts`.
|
||||||
|
- [2026-02-20T11:42:39Z] progress: `src/config/service.ts` cleanup: centralized state application in `applyResolvedConfig(...)`, split path/content parsing into `resolveExistingConfigPath(...)` + `parseConfigContent(...)`, and simplified reload/save flows to remove duplicated assignment logic.
|
||||||
|
- [2026-02-20T11:42:39Z] progress: `src/anki-integration.ts` constructor decomposition: extracted `normalizeConfig(...)`, `createKnownWordCache(...)`, `createPollingRunner(...)`, `createCardCreationService(...)`, and `createFieldGroupingService(...)` to shrink constructor complexity and improve navigation.
|
||||||
|
- [2026-02-20T11:42:39Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + focused suites pass for `dist/config/config.test.js` and `dist/anki-integration.test.js` (37/37).
|
||||||
|
- [2026-02-20T11:42:39Z] scope: staging `src/config/service.ts`, `src/anki-integration.ts`, and subagent bookkeeping only; preserving external subagent INDEX row addition as approved.
|
||||||
- [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 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: 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] progress: added parity tests `src/main/runtime/numeric-shortcut-session-runtime-handlers.test.ts` and `src/main/runtime/overlay-shortcuts-runtime-handlers.test.ts`.
|
||||||
|
|||||||
@@ -98,7 +98,22 @@ export class AnkiIntegration {
|
|||||||
}) => Promise<KikuFieldGroupingChoice>,
|
}) => Promise<KikuFieldGroupingChoice>,
|
||||||
knownWordCacheStatePath?: string,
|
knownWordCacheStatePath?: string,
|
||||||
) {
|
) {
|
||||||
this.config = {
|
this.config = this.normalizeConfig(config);
|
||||||
|
this.client = new AnkiConnectClient(this.config.url!);
|
||||||
|
this.mediaGenerator = new MediaGenerator();
|
||||||
|
this.timingTracker = timingTracker;
|
||||||
|
this.mpvClient = mpvClient;
|
||||||
|
this.osdCallback = osdCallback || null;
|
||||||
|
this.notificationCallback = notificationCallback || null;
|
||||||
|
this.fieldGroupingCallback = fieldGroupingCallback || null;
|
||||||
|
this.knownWordCache = this.createKnownWordCache(knownWordCacheStatePath);
|
||||||
|
this.pollingRunner = this.createPollingRunner();
|
||||||
|
this.cardCreationService = this.createCardCreationService();
|
||||||
|
this.fieldGroupingService = this.createFieldGroupingService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeConfig(config: AnkiConnectConfig): AnkiConnectConfig {
|
||||||
|
return {
|
||||||
...DEFAULT_ANKI_CONNECT_CONFIG,
|
...DEFAULT_ANKI_CONNECT_CONFIG,
|
||||||
...config,
|
...config,
|
||||||
fields: {
|
fields: {
|
||||||
@@ -131,15 +146,10 @@ export class AnkiIntegration {
|
|||||||
...(config.isKiku ?? {}),
|
...(config.isKiku ?? {}),
|
||||||
},
|
},
|
||||||
} as AnkiConnectConfig;
|
} as AnkiConnectConfig;
|
||||||
|
}
|
||||||
|
|
||||||
this.client = new AnkiConnectClient(this.config.url!);
|
private createKnownWordCache(knownWordCacheStatePath?: string): KnownWordCacheManager {
|
||||||
this.mediaGenerator = new MediaGenerator();
|
return new KnownWordCacheManager({
|
||||||
this.timingTracker = timingTracker;
|
|
||||||
this.mpvClient = mpvClient;
|
|
||||||
this.osdCallback = osdCallback || null;
|
|
||||||
this.notificationCallback = notificationCallback || null;
|
|
||||||
this.fieldGroupingCallback = fieldGroupingCallback || null;
|
|
||||||
this.knownWordCache = new KnownWordCacheManager({
|
|
||||||
client: {
|
client: {
|
||||||
findNotes: async (query, options) =>
|
findNotes: async (query, options) =>
|
||||||
(await this.client.findNotes(query, options)) as unknown,
|
(await this.client.findNotes(query, options)) as unknown,
|
||||||
@@ -149,7 +159,10 @@ export class AnkiIntegration {
|
|||||||
knownWordCacheStatePath,
|
knownWordCacheStatePath,
|
||||||
showStatusNotification: (message: string) => this.showStatusNotification(message),
|
showStatusNotification: (message: string) => this.showStatusNotification(message),
|
||||||
});
|
});
|
||||||
this.pollingRunner = new PollingRunner({
|
}
|
||||||
|
|
||||||
|
private createPollingRunner(): PollingRunner {
|
||||||
|
return new PollingRunner({
|
||||||
getDeck: () => this.config.deck,
|
getDeck: () => this.config.deck,
|
||||||
getPollingRate: () => this.config.pollingRate || DEFAULT_ANKI_CONNECT_CONFIG.pollingRate,
|
getPollingRate: () => this.config.pollingRate || DEFAULT_ANKI_CONNECT_CONFIG.pollingRate,
|
||||||
findNotes: async (query, options) =>
|
findNotes: async (query, options) =>
|
||||||
@@ -169,7 +182,10 @@ export class AnkiIntegration {
|
|||||||
logInfo: (...args) => log.info(args[0] as string, ...args.slice(1)),
|
logInfo: (...args) => log.info(args[0] as string, ...args.slice(1)),
|
||||||
logWarn: (...args) => log.warn(args[0] as string, ...args.slice(1)),
|
logWarn: (...args) => log.warn(args[0] as string, ...args.slice(1)),
|
||||||
});
|
});
|
||||||
this.cardCreationService = new CardCreationService({
|
}
|
||||||
|
|
||||||
|
private createCardCreationService(): CardCreationService {
|
||||||
|
return new CardCreationService({
|
||||||
getConfig: () => this.config,
|
getConfig: () => this.config,
|
||||||
getTimingTracker: () => this.timingTracker,
|
getTimingTracker: () => this.timingTracker,
|
||||||
getMpvClient: () => this.mpvClient,
|
getMpvClient: () => this.mpvClient,
|
||||||
@@ -236,7 +252,10 @@ export class AnkiIntegration {
|
|||||||
this.previousNoteIds.add(noteId);
|
this.previousNoteIds.add(noteId);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.fieldGroupingService = new FieldGroupingService({
|
}
|
||||||
|
|
||||||
|
private createFieldGroupingService(): FieldGroupingService {
|
||||||
|
return new FieldGroupingService({
|
||||||
getEffectiveSentenceCardConfig: () => this.getEffectiveSentenceCardConfig(),
|
getEffectiveSentenceCardConfig: () => this.getEffectiveSentenceCardConfig(),
|
||||||
isUpdateInProgress: () => this.updateInProgress,
|
isUpdateInProgress: () => this.updateInProgress,
|
||||||
getDeck: () => this.config.deck,
|
getDeck: () => this.config.deck,
|
||||||
|
|||||||
@@ -96,12 +96,7 @@ export class ConfigService {
|
|||||||
|
|
||||||
reloadConfig(): ResolvedConfig {
|
reloadConfig(): ResolvedConfig {
|
||||||
const { config, path: configPath } = this.loadRawConfig();
|
const { config, path: configPath } = this.loadRawConfig();
|
||||||
this.rawConfig = config;
|
return this.applyResolvedConfig(config, configPath);
|
||||||
this.configPathInUse = configPath;
|
|
||||||
const { resolved, warnings } = this.resolveConfig(config);
|
|
||||||
this.resolvedConfig = resolved;
|
|
||||||
this.warnings = warnings;
|
|
||||||
return this.getConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadConfigStrict(): ReloadConfigStrictResult {
|
reloadConfigStrict(): ReloadConfigStrictResult {
|
||||||
@@ -111,15 +106,11 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { config, path: configPath } = loadResult;
|
const { config, path: configPath } = loadResult;
|
||||||
this.rawConfig = config;
|
const resolvedConfig = this.applyResolvedConfig(config, configPath);
|
||||||
this.configPathInUse = configPath;
|
|
||||||
const { resolved, warnings } = this.resolveConfig(config);
|
|
||||||
this.resolvedConfig = resolved;
|
|
||||||
this.warnings = warnings;
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
config: this.getConfig(),
|
config: resolvedConfig,
|
||||||
warnings: [...warnings],
|
warnings: this.getWarnings(),
|
||||||
path: configPath,
|
path: configPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -132,11 +123,7 @@ export class ConfigService {
|
|||||||
? this.configPathInUse
|
? this.configPathInUse
|
||||||
: this.configFileJsonc;
|
: this.configFileJsonc;
|
||||||
fs.writeFileSync(targetPath, JSON.stringify(config, null, 2));
|
fs.writeFileSync(targetPath, JSON.stringify(config, null, 2));
|
||||||
this.rawConfig = config;
|
this.applyResolvedConfig(config, targetPath);
|
||||||
this.configPathInUse = targetPath;
|
|
||||||
const { resolved, warnings } = this.resolveConfig(config);
|
|
||||||
this.resolvedConfig = resolved;
|
|
||||||
this.warnings = warnings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
patchRawConfig(patch: RawConfig): void {
|
patchRawConfig(patch: RawConfig): void {
|
||||||
@@ -159,11 +146,7 @@ export class ConfigService {
|
|||||||
error: string;
|
error: string;
|
||||||
path: string;
|
path: string;
|
||||||
} {
|
} {
|
||||||
const configPath = fs.existsSync(this.configFileJsonc)
|
const configPath = this.resolveExistingConfigPath();
|
||||||
? this.configFileJsonc
|
|
||||||
: fs.existsSync(this.configFileJson)
|
|
||||||
? this.configFileJson
|
|
||||||
: this.configFileJsonc;
|
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath)) {
|
||||||
return { ok: true, config: {}, path: configPath };
|
return { ok: true, config: {}, path: configPath };
|
||||||
@@ -171,19 +154,7 @@ export class ConfigService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = fs.readFileSync(configPath, 'utf-8');
|
const data = fs.readFileSync(configPath, 'utf-8');
|
||||||
const parsed = configPath.endsWith('.jsonc')
|
const parsed = this.parseConfigContent(configPath, data);
|
||||||
? (() => {
|
|
||||||
const errors: ParseError[] = [];
|
|
||||||
const result = parseJsonc(data, errors, {
|
|
||||||
allowTrailingComma: true,
|
|
||||||
disallowComments: false,
|
|
||||||
});
|
|
||||||
if (errors.length > 0) {
|
|
||||||
throw new Error(`Invalid JSONC (${errors[0]?.error ?? 'unknown'})`);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
})()
|
|
||||||
: JSON.parse(data);
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
config: isObject(parsed) ? (parsed as Config) : {},
|
config: isObject(parsed) ? (parsed as Config) : {},
|
||||||
@@ -195,6 +166,41 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyResolvedConfig(config: RawConfig, configPath: string): ResolvedConfig {
|
||||||
|
this.rawConfig = config;
|
||||||
|
this.configPathInUse = configPath;
|
||||||
|
const { resolved, warnings } = this.resolveConfig(config);
|
||||||
|
this.resolvedConfig = resolved;
|
||||||
|
this.warnings = warnings;
|
||||||
|
return this.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveExistingConfigPath(): string {
|
||||||
|
if (fs.existsSync(this.configFileJsonc)) {
|
||||||
|
return this.configFileJsonc;
|
||||||
|
}
|
||||||
|
if (fs.existsSync(this.configFileJson)) {
|
||||||
|
return this.configFileJson;
|
||||||
|
}
|
||||||
|
return this.configFileJsonc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseConfigContent(configPath: string, data: string): unknown {
|
||||||
|
if (!configPath.endsWith('.jsonc')) {
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors: ParseError[] = [];
|
||||||
|
const result = parseJsonc(data, errors, {
|
||||||
|
allowTrailingComma: true,
|
||||||
|
disallowComments: false,
|
||||||
|
});
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(`Invalid JSONC (${errors[0]?.error ?? 'unknown'})`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private resolveConfig(raw: RawConfig): {
|
private resolveConfig(raw: RawConfig): {
|
||||||
resolved: ResolvedConfig;
|
resolved: ResolvedConfig;
|
||||||
warnings: ConfigValidationWarning[];
|
warnings: ConfigValidationWarning[];
|
||||||
|
|||||||
Reference in New Issue
Block a user