d3ro-voice/docs/design/04-verification-report.md
Yun Chan e24bb8378c 초기 프로젝트 설정: 하네스 시스템 + 설계서 + RE 노하우
- CLAUDE.md: 프로젝트 규칙, 기술 스택, 코딩 규칙, 페이즈 로드맵
- .claude/settings.json: 권한, 강제 훅 (매 프롬프트 설계서 규칙 주입)
- .claude/skills/: implement-phase, review-phase, scaffold, test-commit, debug
- .claude/agents/: electron-architect, voice-pipeline-expert, ui-specialist
- docs/design/00-09: 마스터 아키텍처, 서비스 명세(16개), IPC(113채널),
  DB스키마, UI컴포넌트, 검증리포트, 외부엔진연동, 갭분석,
  VoiceMode패턴, 디자인시스템, 히스토리팝업
- docs/phases/1-7+3.5: 전체 구현 페이즈 문서
- docs/re-findings/: Speakly RE 노하우 5개 문서
2026-04-05 01:03:03 +09:00

392 lines
22 KiB
Markdown

# 04. 설계서 vs Speakly 소스 대조 검증 리포트
> 검증일: 2026-04-04
> 검증 대상: 설계서 00~03 vs Speakly 추출 소스 (`/tmp/speakly-extracted/dist/src/`)
> 검증자: verifier agent
---
## 요약
| 등급 | 건수 | 설명 |
|------|------|------|
| **Critical** | 5 | 설계서가 잘못 반영하거나 핵심 로직이 누락된 항목 |
| **Major** | 10 | 기능 동작에 영향을 줄 수 있는 불일치/누락 |
| **Minor** | 8 | 정확도 개선이 필요한 세부 사항 |
---
## 1. 상태 머신 검증
### CRITICAL-01: RecognitionState에 CONNECTING, DESTROYED 누락
- **설계서 위치**: `02-ipc-and-types.md` 394-403행, `01-service-specifications.md` 559-569행
- **실제 소스 위치**: `types/recording.js` 34-54행
- **차이점**: Speakly `RecognitionState`는 9개 상태를 정의한다:
- `IDLE`, `PREPARING`, **`CONNECTING`**, `READY`, `RECOGNIZING`, `COMPLETED`, `CANCELLED`, `ERROR`, **`DESTROYED`**
- 설계서 `02`에는 `CONNECTING` 누락, `COMPLETING` 상태가 추가되어 있다 (Speakly에 없음).
- 설계서 `01`에는 `Processing` 상태가 추가되어 있으나, Speakly에는 `Processing`이 없고 `RECOGNIZING` 상태에서 서버 측 처리가 진행된다.
- `DESTROYED`는 dispose 전용 최종 상태로, 설계서에 누락.
- **수정 제안**:
- `CONNECTING` 상태 추가 (WebSocket/sidecar 연결 중)
- `COMPLETING``RECOGNIZING`의 하위 플래그로 변경하거나 제거
- `DESTROYED` 상태 추가 (리소스 정리 완료 전용)
- `Processing`은 로컬에서는 LLM 후처리 시 의미가 있으나, Speakly의 원래 상태와 다름을 명시
### MAJOR-01: AudioState는 일치
- **결과**: 설계서 `AudioState` (`IDLE`, `INITIALIZING`, `STREAMING`, `STOPPED`) = Speakly 소스 완전 일치. 문제 없음.
### MAJOR-02: RecordingTipUIState 누락
- **실제 소스 위치**: `types/recording.js` 60-76행
- **차이점**: Speakly에는 `RecordingTipUIState` 열거형이 별도 존재:
- `OPENING`, `RECORDING`, `THINKING`, `CANCELLED`, `NOTICE`, `ERROR`, `ERROR_WITH_RETRY`
- 설계서에 RecordingTip UI 상태 매핑이 없다.
- **수정 제안**: `02-ipc-and-types.md` 또는 `03-db-and-ui.md`에 RecordingTipUIState 추가
---
## 2. 이벤트 페이로드 검증
### MAJOR-03: VoiceMode 열거형 불일치
- **설계서 위치**: `01-service-specifications.md` 579-585행, `02-ipc-and-types.md` 388행
- **실제 소스 위치**: `VoiceModeService.js` 89-95행
- **차이점**: Speakly `VoiceMode`는 4개 모드:
- `DICTATION`, `HANDS_FREE`, **`CUSTOM_INSTRUCTION`**, **`HANDS_FREE_NO_WAKE`**
- 설계서는 `dictation``hands-free` 2개만 정의.
- **수정 제안**:
- `custom-instruction` 모드는 D3RO의 CustomInstructionService로 매핑 가능, 설계서에 명시 필요
- `hands-free-no-wake` 모드는 검증 항목 3에서 별도 기술
### MAJOR-04: HotkeyService 이벤트 구조 차이
- **설계서 위치**: `00-master-architecture.md` 257-262행
- **실제 소스 위치**: `HotkeyService.js` 35-51행
- **차이점**:
- 설계서: `hotkey:dictation-pressed`, `hotkey:dictation-released`, `hotkey:dictation-double-press`, `hotkey:command-pressed`
- Speakly 실제: 범용 `hotkey:pressed` / `hotkey:released` 이벤트에 `commandId` 페이로드 포함
- 더블 프레스는 HotkeyService가 아니라 VoiceModeService에서 타이밍으로 감지
- **수정 제안**: HotkeyService 이벤트를 범용 `hotkey:pressed(commandId, timestamp)` / `hotkey:released(commandId, timestamp)` 패턴으로 변경. 더블 프레스 감지는 VoiceModeService 책임으로 명시.
### MINOR-01: 오디오 샘플레이트 불일치
- **설계서 위치**: `01-service-specifications.md` 68행 (`sampleRate: 16_000`)
- **실제 소스 위치**: `config/constants.js` 47행 (`SAMPLE_RATE: 24000`)
- **차이점**: 설계서는 Whisper 기본인 16kHz를 기술하나, Speakly는 24kHz로 캡처한다 (Opus 인코딩 후 서버 전송).
- **수정 제안**: D3RO는 로컬 Whisper를 사용하므로 16kHz가 맞을 수 있으나, Speakly 원본이 24kHz임을 각주로 명시. 코덱이 Opus인 경우 24kHz가 필요할 수 있음.
---
## 3. 누락 패턴 검증
### CRITICAL-02: accidentalPress 감지 패턴 미기술
- **실제 소스 위치**: `VoiceModeService.js` 116행, 3251-3293행
- **패턴 설명**:
- `MIN_AUDIO_DURATION_MS = 700ms` 미만 키 누름은 accidental press로 판정
- 두 가지 판정 기준: (1) key press duration < 700ms, (2) session lifetime < 700ms
- 세션에 `accidentalPress = true` 마킹 즉시 취소
- `checkAndMarkAccidentalPress()` 메서드로 녹음 정지 전에 조기 체크
- **설계서 상태**: 완전 누락
- **수정 제안**: `01-service-specifications.md` VoiceModeService 섹션에 accidentalPress 감지 로직 추가:
```
키 릴리스 시점에서 duration < MIN_AUDIO_DURATION_MS(700ms) → 세션 취소
```
### CRITICAL-03: Audio Mute 연동 패턴 미기술
- **실제 소스 위치**: `VoiceModeService.js` 118-120행, 2726-2810행
- **패턴 설명**:
- 녹음 시작 시 시스템 오디오 음소거 (`NativeService.muteSystemAudio()`)
- `MUTE_DELAY_MS = 500ms` — 사운드 이펙트 재생 후 음소거
- `wasMutedBeforeRecording` — 이미 음소거였으면 녹음 후 언뮤트 안함
- `UNMUTE_SOUND_DELAY_MS = 100ms` — 언뮤트 후 종료 효과음 재생 지연
- `config:getMuteAudioWhenDictating` 설정으로 on/off 가능
- 루프백 마이크 사용 시 음소거 스킵 (`_isLoopbackMic()`)
- **설계서 상태**: 완전 누락
- **수정 제안**: `01-service-specifications.md` VoiceModeService에 mute/unmute 시퀀스 추가
### CRITICAL-04: Retry 로직 (이전 세션 오디오 재전송) 미기술
- **실제 소스 위치**: `VoiceModeService.js` 181-182행, 2563-2594행, 3316-3320행+
- **패턴 설명**:
- `retryRecognition(historyId)` — 이전 세션의 저장된 오디오 파일을 읽어 재전송
- `retryingSession` / `retryingHistoryId` 상태 추적
- `history:retry` IPC 채널로 렌더러에서 호출 가능
- ESC 키로 retry 취소 가능
- No-wake 모드 취소 시 `undoNoWakeCancel()` → 자동 retry
- 오디오 파일이 디스크에 저장된 세션만 retry 가능
- **설계서 상태**: 완전 누락
- **수정 제안**: 별도 섹션으로 Retry 흐름도 추가
### CRITICAL-05: Action Queue (이벤트 직렬화) 패턴 미기술
- **실제 소스 위치**: `VoiceModeService.js` 186-275행
- **패턴 설명**:
- `actionQueue: NXAction[]` — 키보드 이벤트를 큐에 넣고 순차 처리
- `isProcessingActionQueue` 플래그로 동시 실행 방지
- 핫키 press/release 이벤트 간 race condition 방지
- NXAction 클래스: `type`, `timestamp`, `hotkeyId`, `hotkeyTimestamp`
- ESC 시 `clearActionQueue()`로 전체 큐 클리어
- **설계서 상태**: 완전 누락
- **수정 제안**: VoiceModeService 명세에 Action Queue 패턴 추가. 이것은 동시 키 이벤트 안정성의 핵심.
### MAJOR-05: hands-free-no-wake 모드 미기술
- **실제 소스 위치**: `VoiceModeService.js` 94행, 103행, 656-689행
- **패턴 설명**:
- 별도 핫키로 진입하는 핸즈프리 모드
- 녹음 중 취소 시 오디오를 디스크에 보존 (`cancelledSessionId`)
- 나중에 `undoNoWakeCancel()`로 자동 retry 가능
- `getNoWakeModeEnabled()` 설정으로 on/off
- **설계서 상태**: 완전 누락
- **수정 제안**: VoiceModeService VoiceMode 열거형에 추가, 전용 핫키 등록 로직 기술
### MAJOR-06: RecordingTip 2-phase 리사이즈는 기술됨, 하지만 preload 전략 누락
- **설계서 위치**: `02-ipc-and-types.md` 159-166행 (tipMeasured, tipPrepare, tipShow)
- **실제 소스 위치**: `RecordingTipWindow.js` 157행 (`preload()` 메서드)
- **차이점**: 설계서는 2-phase IPC는 기술했으나, RecordingTipWindow의 `preload()` 메서드 — 앱 시작 시 윈도우를 미리 생성해놓고 숨겨두는 전략이 누락.
- **수정 제안**: 초기화 시퀀스(00-master 단계 10)에 "팝업 윈도우 프리로드" 설명 보강
### MAJOR-07: 마이크 디바이스 팁(NewMicrophonePrompt) 미기술
- **실제 소스 위치**: `main/index.js` 74행, 1090-1108행, `main/NewMicrophonePromptWindow.js`
- **패턴 설명**:
- 새 마이크 디바이스 연결 시 전용 프롬프트 윈도우 표시
- "이 마이크로 전환?" + "다시 묻지 않기" 옵션
- `config:getDontPromptNewMicrophone` / `config:setShowMicDeviceTip` 설정
- `NewMicrophonePromptWindow` 클래스 — 독립 윈도우
- **설계서 상태**: 완전 누락
- **수정 제안**: `03-db-and-ui.md`에 NewMicrophonePrompt 윈도우 추가
### MAJOR-08: ResultPopup auto-close 타이밍 미기술
- **실제 소스 위치**: `main/ResultPopupWindow.js` 27-28행, 249-269행
- **패턴 설명**:
- 기본 `autoCloseDelay = 10000ms` (10초)
- 마우스 호버 시 타이머 일시 정지
- 마우스 떠나면 타이머 재개
- `autoCloseDelay: 0`이면 auto-close 비활성화
- `content-ready` 이벤트 시 타이머 시작
- **설계서 상태**: `02-ipc-and-types.md`에 `window:showResultPopup` 채널은 있으나 auto-close 동작 미기술
- **수정 제안**: ResultPopup 동작 명세에 auto-close 타이밍 추가
### MAJOR-09: Typing Nudge 패턴 미기술
- **실제 소스 위치**: `VoiceModeService.js` 196-208행, 706-796행
- **패턴 설명**:
- 사용자가 5초간 연속 타이핑 (10키 이상) 시 "음성 사용해보세요" 넛지 표시
- 하루 최대 3회, 30분 간격
- 오늘 이미 음성 사용했으면 억제
- 자정 넘으면 자동 리셋
- `typing-nudge` 이벤트 emit
- **설계서 상태**: 완전 누락
- **수정 제안**: D3RO에서 이 기능이 필요한지 결정 후, 채택 시 VoiceModeService 명세에 추가
### MAJOR-10: Mode Switch 패턴 (녹음 중 모드 전환) 미기술
- **실제 소스 위치**: `VoiceModeService.js` 626-651행
- **패턴 설명**:
- 녹음 중 다른 모드 핫키 누르면 오디오 청크를 보존하며 모드 전환
- `savedAudioChunks = [...this.audioChunks]` → cancelRecording → startRecording(savedAudioChunks)
- `sessionType: 'mode-switch'` — 시작 사운드 억제, UI 전환 최소화
- **설계서 상태**: 완전 누락
- **수정 제안**: VoiceModeService 시퀀스 다이어그램에 mode-switch 분기 추가
---
## 4. IPC 채널 누락 분석
설계서 IPC 채널 수: **113개** (02-ipc-and-types.md 기준)
Speakly 실제 IPC 채널 수: **~160개** (preload/index.js에서 추출)
### 의도적 제거 (클라우드/인증 관련) — 문제 없음
| 네임스페이스 | 채널 수 | 이유 |
|---|---|---|
| `auth:*` | 11 | 로컬 전용, 인증 불필요 |
| `feedback:*` | 1 | 클라우드 피드백 |
| `userInfo:*` | 6 | 클라우드 사용자 정보 |
| `update:*` | 8 | Phase 7으로 연기 |
| `logUpload:*` | 4 | 클라우드 로그 업로드 |
| `banner:*` | 4 | 클라우드 배너 |
| `report:*` | 1 | 클라우드 오류 보고 |
| `diagnostics:*` | 3 | 디버그 진단 |
| `debug:*` | 1 | 접근성 트리 탐색 |
### 실수 누락 가능성 (검토 필요)
| 채널명 | 설계서 | Speakly | 판정 |
|--------|--------|---------|------|
| `history:retry` | 없음 | 있음 | **누락** — retry 기능에 필수 |
| `history:getLatestId` | 없음 | 있음 | **누락** — undo/retry에 사용 |
| `history:getRecentSessions` | 없음 | 있음 | **누락** — 대시보드에 사용 |
| `history:deleteByDuration` | 없음 | 있음 | 선택적 — 짧은 녹음 일괄 삭제 |
| `history:getStats` / `resetStats` | 없음 | 있음 | `stats:*`로 통합된 것으로 보이나 확인 필요 |
| `config:getMuteAudioWhenDictating` | 없음 | 있음 | **누락** — mute 연동 설정 |
| `config:getSelectedMicrophone` | 없음 | 있음 | **누락** — 마이크 선택 저장 |
| `config:setSelectedMicrophone` | 없음 | 있음 | **누락** |
| `config:getTranslateLanguage` | 없음 | 있음 | **누락** — 번역 언어 설정 |
| `config:getSoundEffectsEnabled` | 없음 | 있음 | **누락** — `system:isSoundEnabled`으로 매핑? |
| `config:getDontPromptNewMicrophone` | 없음 | 있음 | **누락** — 마이크 프롬프트 설정 |
| `config:shouldShowHandsFreePromo` | 없음 | 있음 | 선택적 — 프로모션 UI |
| `hotkey:getHandsFreeNoWakeShortcut` | 없음 | 있음 | **누락** — no-wake 모드 핫키 |
| `hotkey:setHandsFreeNoWakeShortcut` | 없음 | 있음 | **누락** |
| `hotkey:getVoiceModeEnabled` | 없음 | 있음 | **누락** — 모드별 활성화 토글 |
| `hotkey:setVoiceModeEnabled` | 없음 | 있음 | **누락** |
| `hotkey:pauseVoiceMode` | 없음 | 있음 | **누락** — 설정 UI 중 핫키 일시정지 |
| `hotkey:resumeVoiceMode` | 없음 | 있음 | **누락** |
| `hotkey:validateHotkey` | 없음 | 있음 | **누락** — 핫키 유효성 검증 |
| `hotkey:resetToDefault` | 없음 | 있음 | **누락** — 기본값 복원 |
| `hotkey:getAvailableKeys` | 없음 | 있음 | **누락** — 사용 가능한 키 목록 |
| `hotkey:setOverrideAppBundleId` | 없음 | 있음 | 선택적 — 온보딩/테스트 전용 |
| `hotkey:refreshRequired` | 없음 | 있음 | **누락** — 핫키 재등록 알림 |
| `clipboard:copy` | 없음 | 있음 | **누락** — 결과 클립보드 복사 |
| `input:insertText` | 없음 | 있음 | `system:insertText`로 매핑됨, 확인 필요 |
| `input:getCursorState` | 없음 | 있음 | 선택적 — postInsertCursorContext |
| `app:getActiveApplication` | 없음 | 있음 | `system:getActiveApp`으로 매핑됨 |
| `app:getAppIcon` | 없음 | 있음 | 선택적 — 앱 아이콘 표시 |
| `voice:startWithCustomInstruction` | 없음 | 있음 | **누락** — Custom Instruction 녹음 |
| `voice:stopForCustomInstruction` | 없음 | 있음 | **누락** |
| `voice:cancel` | 없음 | 있음 | **누락** — 별도 취소 채널 (voice:cancelRecording과 다름) |
| `customInstruction:*` (9개+) | 있음(일부) | 있음(많음) | 키 이벤트 관련 채널 다수 누락 |
---
## 5. 타이밍 상수 검증
| 상수 | 설계서 값 | Speakly 실제 값 | 일치 | 비고 |
|------|----------|-----------------|------|------|
| 오디오 샘플레이트 | 16,000 Hz | **24,000 Hz** | **불일치** | 설계서는 Whisper 기본, Speakly는 Opus 기반 |
| 오디오 프레임 크기 | 60ms | 60ms | 일치 | |
| 바이트/프레임 | 1,920 | 계산 시 **2,880** (24000*2*0.06) | **불일치** | 샘플레이트 차이에 따름 |
| 디바이스 폴링 간격 | 2초 | 확인 필요 | - | |
| ERROR_AUTO_HIDE_DELAY | 미기술 | **3,000ms** | 누락 | |
| ERROR_WITH_RETRY_AUTO_HIDE_DELAY | 미기술 | **10,000ms** | 누락 | |
| NOTICE_AUTO_HIDE_DELAY | 미기술 | **3,000ms** | 누락 | |
| MAX_RETRY_ATTEMPTS | 미기술 | **3** | 누락 | |
| PRESS_HOLD_THRESHOLD | 미기술 | **10ms** | 누락 | Speakly 주석에 "changed from 300ms" |
| SECOND_PRESS_THRESHOLD | 미기술 | **300ms** | 누락 | 더블 프레스 감지 |
| STOP_DELAY_MS | 미기술 | **200ms** | 누락 | 키 릴리스 후 정지 지연 |
| MIN_AUDIO_DURATION_MS | 미기술 | **700ms** | 누락 | accidentalPress 판정 |
| MUTE_DELAY_MS | 미기술 | **500ms** | 누락 | 사운드 이펙트 후 음소거 |
| UNMUTE_SOUND_DELAY_MS | 미기술 | **100ms** | 누락 | 언뮤트 후 효과음 지연 |
| POST_RECORDING_WAIT_TIME | 미기술 | **4,000ms** | 누락 | 녹음 후 연결 대기 |
| RETRY_MAX_CONNECT_WAIT_TIME | 미기술 | **15,000ms** | 누락 | 파일 리플레이 연결 대기 |
| FINISH_IDLE_TIMEOUT | 미기술 | **30,000ms** | 누락 | commit 후 완료 대기 |
| FINISH_MAX_WAIT | 미기술 | **120,000ms** | 누락 | 완료 절대 최대 대기 |
| ResultPopup autoCloseDelay | 미기술 | **10,000ms** | 누락 | |
| HEARTBEAT_INTERVAL | 미기술 | **5,000ms** | 누락 | WebSocket 핑 간격 |
| HEARTBEAT_TIMEOUT | 미기술 | **15,000ms** | 누락 | 서버 응답 타임아웃 |
| SESSION_MONITOR_INTERVAL | 미기술 | **5,000ms** | 누락 | 세션 모니터링 타이머 |
| SYNC_DELAY_MS (HotkeyService) | 미기술 | **500ms** | 누락 | 핫키 동기화 디바운스 |
**수정 제안**: `01-service-specifications.md` 각 서비스 섹션에 타이밍 상수 표 추가. D3RO 고유 값이 다를 경우 주석으로 Speakly 원본 값 명시.
---
## 6. DB 스키마 검증
### MINOR-02: history 테이블 컬럼 차이
| 컬럼 | 설계서 | Speakly 실제 | 판정 |
|------|--------|-------------|------|
| `focused_app_bundle_id` | 제거됨 | **있음** | 설계서에서 의도적 제거 (macOS 전용) → OK |
| `window_web_title` | 제거됨 | **있음** | 설계서에서 의도적 제거 → OK |
| `window_web_domain` | 제거됨 | **있음** | 설계서에서 의도적 제거 → OK |
| `window_web_url` | 제거됨 | **있음** | 설계서에서 의도적 제거 → OK |
| `audio_metadata` | 제거됨 | **있음** (JSON) | 설계서에서 의도적 제거 → OK |
| `mic_device_info` | 제거됨 | **있음** (JSON) | 설계서에서 의도적 제거 → OK |
| `user_id` | 제거됨 | **있음** | 로컬 단일 사용자 → OK |
| `error_code` | **추가됨** | 없음 | D3RO 신규 — OK |
| `stt_model` | **추가됨** | 없음 | D3RO 신규 — OK |
| `llm_model` | **추가됨** | 없음 | D3RO 신규 — OK |
| `stt_latency_ms` | **추가됨** | 없음 | D3RO 신규 — OK |
| `llm_latency_ms` | **추가됨** | 없음 | D3RO 신규 — OK |
| `selected_text` | 미언급 | **없음** | 설계서 "제거" 목록에 있으나 Speakly에도 없음 → 각주 정정 필요 |
### MINOR-03: history 모드 열거형 차이
- 설계서: `'dictation' | 'translate' | 'command'`
- Speakly: `'dictation' | 'hands-free' | 'translation'`
- **수정 제안**: D3RO 고유 모드명 사용은 문제 없으나, Speakly 원본과의 매핑을 각주로 명시
### MINOR-04: stats 테이블 차이
- 설계서에 `streak_days`, `last_session_at` 추가 — Speakly에 없는 D3RO 신규 필드 → OK
- Speakly의 `stats` 테이블은 설계서와 동일한 구조 (id=1 싱글턴)
### MINOR-05: 인덱스 차이
- 설계서: `idx_history_created_at` (단일 컬럼)
- Speakly: `idx_history_user_created_at` (user_id, created_at 복합 인덱스)
- 로컬 단일 사용자이므로 설계서의 단순화가 적절 → OK
---
## 7. 추가 발견 사항
### MINOR-06: NativeService koffi+DLL 대체 방안 명확화 필요
- **실제 소스**: `NativeService.js` 44행 — `koffi` 모듈로 네이티브 DLL 로드
- **설계서 위치**: `00-master-architecture.md` 166행 — "제거, npm 패키지로 대체"
- **이슈**: NativeService는 키보드 모니터링, 마이크 캡처, 시스템 오디오 음소거, 앱 모니터링 등 광범위한 기능 제공. 이를 대체할 npm 패키지 목록이 불완전.
- **수정 제안**: NativeService가 제공하는 기능별 대체 패키지 매핑 표 작성:
- 키보드 모니터링 → `uiohook-napi` (기술됨)
- 마이크 캡처 → 미명시 (Web Audio API? node-audiorecorder?)
- 시스템 오디오 음소거 → 미명시 (`loudness`? `@aspect-build/napi-audio`?)
- 앱 모니터링 → 미명시 (`active-win`?)
- 클립보드 → `@nut-tree/nut` (기술됨)
- 텍스트 삽입 → `@nut-tree/nut` (기술됨)
- 핫키 인터셉트 → `uiohook-napi` (기술됨)
### MINOR-07: TextOperationStrategy의 앱별 삽입 전략 미기술
- **실제 소스**: `TextOperationStrategy.js` 12-36행
- **패턴**: 앱별로 다른 텍스트 삽입 방법 (clipboard vs keyboard), 선택 방법 (clipboard vs ax vs none), 검증 모드 (auto vs skip) 설정
- **설계서**: `TextInsertService`에서 일률적으로 "clipboard save → set → Ctrl+V → restore" 기술
- **수정 제안**: 앱별 규칙(rules) 시스템 최소한으로 기술. D3RO 로컬 환경에서는 원격 config 불필요하나, 기본 fallback 규칙은 유지 필요.
### MINOR-08: 세션 모니터링 패턴
- **실제 소스**: `VoiceModeService.js` 184-185행, 209-210행
- **패턴**: `sessionMonitorTimer` (5초 간격) — 좀비 세션 감지 및 정리
- **설계서 상태**: 누락
- **수정 제안**: VoiceModeService 안정성 패턴으로 추가
---
## 8. 설계서에 추가해야 할 패턴 목록 (우선순위순)
| # | 패턴 | 중요도 | 대상 설계서 |
|---|------|--------|------------|
| 1 | **Action Queue (이벤트 직렬화)** | Critical | 01-service-specifications.md |
| 2 | **accidentalPress 감지** | Critical | 01-service-specifications.md |
| 3 | **Audio Mute 연동** | Critical | 01-service-specifications.md |
| 4 | **Retry 로직 (오디오 파일 재전송)** | Critical | 01-service-specifications.md |
| 5 | **RecognitionState CONNECTING/DESTROYED** | Critical | 02-ipc-and-types.md |
| 6 | **hands-free-no-wake 모드** | Major | 01-service-specifications.md |
| 7 | **Mode Switch (녹음 중 모드 전환)** | Major | 01-service-specifications.md |
| 8 | **ResultPopup auto-close 타이밍** | Major | 03-db-and-ui.md |
| 9 | **NewMicrophonePrompt 윈도우** | Major | 03-db-and-ui.md |
| 10 | **RecordingTipUIState 열거형** | Major | 02-ipc-and-types.md |
| 11 | **타이밍 상수 표** | Major | 01-service-specifications.md |
| 12 | **누락 IPC 채널 (~25개)** | Major | 02-ipc-and-types.md |
| 13 | **NativeService 대체 패키지 매핑** | Minor | 00-master-architecture.md |
| 14 | **앱별 TextOperation 규칙** | Minor | 01-service-specifications.md |
| 15 | **Typing Nudge** | Minor | 결정 후 추가 |
| 16 | **세션 모니터링 타이머** | Minor | 01-service-specifications.md |
| 17 | **RecordingTip preload 전략** | Minor | 00-master-architecture.md |
---
## 9. 결론
설계서는 Speakly의 기본 아키텍처와 주요 서비스 구조를 잘 반영하고 있으나, **VoiceModeService의 핵심 런타임 패턴**(Action Queue, accidentalPress, mute 연동, retry, mode switch)이 거의 완전히 누락되어 있다. 이 패턴들은 사용자 경험의 안정성과 직결되므로 구현 전에 반드시 설계서에 반영해야 한다.
또한 IPC 채널이 약 25개 누락되어 있으며, 특히 hotkey 관련 채널(pause/resume, validate, availableKeys, no-wake 단축키)과 history:retry 채널이 중요하다.
상태 머신은 대체로 일치하나 `CONNECTING`과 `DESTROYED` 상태가 빠져 있어 상태 전이 다이어그램 수정이 필요하다.