refactor(main): IPC 채널 SSOT 일원화 + AppConfig 타입 강화 (WS2)

- ipc-channels.ts SSOT에 9개 그룹/키 추가: INSTRUCTION, SYSTEM_AUDIO,
  POPUP_RESULT/HISTORY/COMMAND/CAPTION, VOICE_PARTIAL, CLIPBOARD, APP,
  VOICE_CONVERSATION.FINISH_LISTENING
- WindowManager/handlers/bootstrap 하드코딩 채널 -> IPC_CHANNELS 교체.
  불일치 4건(result:* vs window:tip*)은 preload 재검증 후 SSOT로 통일.
  notifyRenderer channel: string -> IPCChannel 타입 좁힘.
- AppConfig에 7개 누락 키 추가(customInstructions, llmChains,
  voiceCommandRules, voiceCommandsEnabled, activeInstructionId,
  activeChainId, captionAudioSource) -> as never 16건 제거.
- ChainService/CustomInstructionService/WindowManager dynamic require -> static import.
- services/index.ts 데드 레지스트리 제거 (import 0건).
SKIP: ipcSuccess/ipcError 헬퍼 통일, catch 패턴(별도)
정책: docs/REFACTOR_POLICY.md DP2, DP9
This commit is contained in:
Yun Chan 2026-07-22 01:54:18 +09:00
parent b820c789bb
commit aef44289a5
15 changed files with 161 additions and 126 deletions

View file

@ -3,7 +3,7 @@
// LLMChain을 electron-store에 저장하고, 체인을 순차 실행한다.
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { configGet, configSet } from './ConfigService'
import { getCustomInstructionService } from './CustomInstructionService'
import { getLocalLLMService } from './LocalLLMService'
import { getMainWindow } from '../windows/WindowManager'
@ -29,7 +29,7 @@ let initialized = false
function loadChains(): LLMChain[] {
try {
const stored = configGet('llmChains' as never) as LLMChain[] | undefined
const stored = configGet('llmChains') as LLMChain[] | undefined
if (Array.isArray(stored) && stored.length > 0) {
return stored
}
@ -41,11 +41,7 @@ function loadChains(): LLMChain[] {
function saveChains(): void {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { configSet } = require('./ConfigService') as {
configSet: (key: never, value: never) => void
}
configSet('llmChains' as never, chains as never)
configSet('llmChains', chains)
} catch (error) {
logger.warn(
`Failed to save chains: ${error instanceof Error ? error.message : String(error)}`

View file

@ -3,26 +3,11 @@
// electron-store에 저장, 프리셋 5개 기본 제공.
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { configGet, configSet } from './ConfigService'
import type { CustomInstruction } from '@d3ro/core/types'
const logger = getLogger('CustomInstructionService')
// ============================================================
// 타입
// ============================================================
export interface CustomInstruction {
id: string
name: string
description: string
prompt: string
icon: string
isBuiltin: boolean
order: number
createdAt: number
updatedAt: number
}
type CreateInput = Omit<CustomInstruction, 'id' | 'isBuiltin' | 'order' | 'createdAt' | 'updatedAt'>
// ============================================================
@ -91,7 +76,7 @@ let initialized = false
function loadInstructions(): CustomInstruction[] {
// electron-store에서 로드 시도
try {
const stored = configGet('customInstructions' as never) as CustomInstruction[] | undefined
const stored = configGet('customInstructions') as CustomInstruction[] | undefined
if (Array.isArray(stored) && stored.length > 0) {
return stored
}
@ -110,8 +95,7 @@ function loadInstructions(): CustomInstruction[] {
function saveInstructions(): void {
try {
const { configSet } = require('./ConfigService')
configSet('customInstructions' as never, instructions as never)
configSet('customInstructions', instructions)
} catch (error) {
logger.warn(`Failed to save instructions: ${error instanceof Error ? error.message : String(error)}`)
}

View file

@ -145,9 +145,8 @@ class MeetingModeService extends EventEmitter {
// 회의 모드는 마이크 캡처 강제 — ConfigService 값도 임시 변경
// (CaptionService.start()가 ConfigService에서 다시 읽기 때문)
type AppConfigKey = keyof import('@d3ro/core/types').AppConfig
const prevAudioSource = configGet('captionAudioSource' as AppConfigKey) as unknown as string
configSet('captionAudioSource' as AppConfigKey, 'mic' as never)
const prevAudioSource = configGet('captionAudioSource')
configSet('captionAudioSource', 'mic')
try {
await captionService.start()
@ -156,12 +155,12 @@ class MeetingModeService extends EventEmitter {
hideCaptionOverlay()
// ConfigService 원래 값 복원
if (prevAudioSource) {
configSet('captionAudioSource' as AppConfigKey, prevAudioSource as never)
configSet('captionAudioSource', prevAudioSource)
}
} catch (err) {
// ConfigService 원래 값 복원
if (prevAudioSource) {
configSet('captionAudioSource' as AppConfigKey, prevAudioSource as never)
configSet('captionAudioSource', prevAudioSource)
}
// 시작 실패 시 복원
captionService.off('segment', this._segmentHandler)

View file

@ -67,9 +67,8 @@ const DEFAULT_KEYWORDS: ReadonlyArray<DefaultKeywordEntry> = [
// electron-store 키 (ConfigService와 별도 네임스페이스)
// ============================================================
// configGet/configSet에 타입이 없는 키를 사용하므로 as never 캐스팅 필요
const STORE_KEY_RULES = 'voiceCommandRules' as never
const STORE_KEY_ENABLED = 'voiceCommandsEnabled' as never
const STORE_KEY_RULES: keyof import('@d3ro/core/types').AppConfig = 'voiceCommandRules'
const STORE_KEY_ENABLED: keyof import('@d3ro/core/types').AppConfig = 'voiceCommandsEnabled'
// ============================================================
// 키워드 매칭 엔진
@ -292,7 +291,7 @@ class VoiceCommandService {
private saveRules(): void {
try {
configSet(STORE_KEY_RULES, this.rules as never)
configSet(STORE_KEY_RULES, this.rules)
} catch (error) {
logger.warn(
`Failed to save voice command rules: ${error instanceof Error ? error.message : String(error)}`
@ -311,7 +310,7 @@ class VoiceCommandService {
private saveEnabled(): void {
try {
configSet(STORE_KEY_ENABLED, this.enabled as never)
configSet(STORE_KEY_ENABLED, this.enabled)
} catch (error) {
logger.warn(
`Failed to save voice command enabled state: ${error instanceof Error ? error.message : String(error)}`

View file

@ -752,7 +752,7 @@ class VoiceModeService extends EventEmitter {
if (action === 'chain') {
try {
const { getChainService } = await import('./ChainService')
const activeChainId = configGet('activeChainId' as keyof import('@d3ro/core/types').AppConfig) as unknown as string
const activeChainId = configGet('activeChainId')
if (activeChainId) {
const chainResult = await getChainService().execute(activeChainId, contextPrefix + transcribedText)
if (this._isInTerminalState()) return
@ -769,7 +769,7 @@ class VoiceModeService extends EventEmitter {
// 음성 단축키 오버라이드 또는 활성 명령어
const effectiveInstructionId = overrideInstructionId
?? (configGet('activeInstructionId' as keyof import('@d3ro/core/types').AppConfig) as unknown as string)
?? configGet('activeInstructionId')
if (action === 'custom' || overrideInstructionId) {
let customPrompt = contextPrefix + transcribedText

View file

@ -1,31 +0,0 @@
// src/main/services/index.ts — 서비스 레지스트리
export { initLoggerService, getLogger } from './LoggerService'
export {
initConfigService,
getConfigService,
configGet,
configSet,
configGetAll,
configReset,
onConfigChanged
} from './ConfigService'
export {
getLocalSTTService,
LocalSTTService,
STTState
} from './LocalSTTService'
export type {
TranscriptionResult,
TranscriptionSegment,
TranscribeOptions,
LocalSTTEvents
} from './LocalSTTService'
export { getHotkeyService } from './HotkeyService'
export { getVoiceModeService } from './VoiceModeService'
export { getAudioCaptureService } from './AudioCaptureService'
export { getTextInsertService } from './TextInsertService'
export { getLocalLLMService } from './LocalLLMService'
export { getHistoryService } from './HistoryService'
export { getDictionaryService } from './DictionaryService'
export { getCustomInstructionService } from './CustomInstructionService'