Commit graph

266 commits

Author SHA1 Message Date
yunchan8804
07c3c3873c fix(ci): Ollama 다운로드를 curl.exe로 전환 — IWR 대용량 파일 hang 해소 2026-04-15 21:13:23 +09:00
yunchan8804
8a8fb63636 fix(ci): download-ollama.ps1 메시지 영문화 — 한글 인코딩으로 PowerShell 파싱 실패 해소 2026-04-15 20:49:00 +09:00
yunchan8804
31902513bd build: SoX Windows 바이너리 직접 번들 (SourceForge 다운로드 차단 우회)
- apps/desktop/resources/sox/sox.exe + 의존 DLL 직접 커밋
- .gitignore 화이트리스트 조정
- download-sox.ps1는 파일 존재 시 자동 스킵하므로 CI에서 그대로 통과
2026-04-15 20:44:12 +09:00
yunchan8804
77114029a8 ci: Windows self-hosted runner 단독 배포 흐름 — Docker 의존 제거
- lint/typecheck/unit-test는 태그 푸시에서 제외(allow_failure 추가)
- release-create 잡을 release-cli 도커 이미지 대신 shell+curl(Invoke-RestMethod)로 전환
- 태그 파이프라인은 package-windows → release-create 2단계, 둘 다 Windows runner에서 실행
2026-04-15 20:07:05 +09:00
yunchan8804
aa65e710ec feat: 배포 파이프라인 — Ollama/sidecar 번들 + NSIS 자동 VC++ + GitLab CI + 온보딩 모달
- sidecar 슬림화: torch/pyannote 제거, ctranslate2 GPU 감지, /diarize 삭제
- Ollama 번들: resources/ollama/에 포터블 바이너리 배치, LocalLLMService 1순위 탐색
- installer.nsh: VC++ 재배포 x64 자동 다운로드(aka.ms 경유) + 사일런트 설치
- electron-builder: extraResources에 ollama 추가, nsis.include로 installer.nsh 연결
- scripts: download-ollama.ps1/sh 신규
- LLM.PULL_MODEL IPC 핸들러 + LocalLLMService.pullModel() 구현 (api/pull 스트리밍)
- 온보딩 모달: gemma4:e4b 미설치 감지 시 자동 표시, 진행률 UI, i18n(ko/en) 키 추가
- .gitlab-ci.yml: Windows 러너에서 sidecar/sox/ollama 준비 후 NSIS 패키징, 태그 시 Release 자동 생성
2026-04-15 19:47:27 +09:00
yunchan8804
d1edad6727 chore: 세션 핸드오프 — RN CLI 전환 빌드 정보 + 다음 작업 목록 2026-04-13 21:15:33 +09:00
yunchan8804
bf36a9d97d feat(mobile): Expo → RN CLI 전환 + Android 빌드 성공
RN 0.85 + React 19 기반 apps/mobile-rn 프로젝트 생성.
6개 화면 이식 (Login, Dash, History, Record, Talk, Settings).
@react-navigation/native + bottom-tabs 네비게이션 구성.
Windows NDK CMake libc++_shared 링크 버그 워크아라운드 포함.
Galaxy Fold (SM_F956N) 무선 디버깅 APK 설치 확인.
2026-04-13 21:13:40 +09:00
yunchan8804
55eb62189a feat(mobile): Phase M-2 D3RO 디자인 시스템 모바일 적용
DS 컴포넌트 9개 (ScreenPanel, WaveBars, AppStatusBar, Header, FilterChip 신규),
5개 탭 + 로그인 화면 D3RO 인스트루먼트 미학 적용,
i18n 연결 (I18nProvider + 모바일 전용 키 80+개),
모노레포 workspaces에 apps/mobile 추가.
2026-04-13 17:32:31 +09:00
yunchan8804
541f04d02b refactor: saasMode 분기 제거 — 항상 SaaS(OAuth only)로 동작
Mac/Windows 환경 불일치 해소. BUILD_TIME 상수, CONFIGURE 채널,
개발자 모드 UI 전부 제거. Supabase 설정은 supabase-config.ts SSOT.
2026-04-13 17:20:55 +09:00
윤찬
feb82abbf7 번역 2026-04-13 14:53:17 +09:00
윤찬
9f7ea1d729 chore: 세션 핸드오프 — V3 모바일 Phase M-1 완료 2026-04-13 03:23:24 +09:00
윤찬
211673bc6c feat(mobile): Phase M-1 모바일 프로젝트 기반 + V3 마스터 플랜
- V3 모바일 마스터 플랜 작성 (docs/v3/00-mobile-master-plan.md)
  11개 Phase, 5주 로드맵, 전략 결정사항 확정
- 7개 디자인 HTML 레퍼런스 저장 (docs/v3/designs/)
- app.json → app.config.ts 전환 + 환경변수 체계
- 5탭 네비게이션 (DASH/HIST/REC FAB/TALK/SET)
- metro.config.js monorepo 격리 (root RN 0.84 충돌 해결)
- ui-native 테마 디자인 목업 색상 동기화
- SafeArea 적용 (useSafeAreaInsets)
- DEV 로그인 우회 (__DEV__ 전용)
- Supabase .env 연동
- iOS 시뮬레이터 검증 완료
2026-04-13 03:22:15 +09:00
윤찬
ffca07d120 chore: 세션 핸드오프 — V2-7 완료 2026-04-13 01:28:55 +09:00
윤찬
8af75a0a1e feat: V2-7 Admin 콘솔 리디자인 + 3단계 권한 + SaaS 전환 + 코드 정리
- Admin CRM: D3RO Console 스타일 전체 적용 (panelSx/tableSx/filterBtnSx)
- 3단계 권한: manager/admin/super_admin (DB + Edge Functions + Frontend)
- 랜딩 페이지: 1회 결제 → 월간/연간 구독 SaaS 모델 (10개 언어)
- SSE 스트리밍: VoiceConversation Premium LLM 라우팅 + fallback
- Supabase 클라이언트: packages/api-client 공통 추출 (browser+server)
- RPC 함수 타입: 9개 정의 (admin_usage_by_feature 등)
- callAdminApi 401 버그 수정 (getUser() 선행 토큰 갱신)
2026-04-13 01:28:10 +09:00
윤찬
d0e854c255 chore: 세션 핸드오프 — Phase V2-6 완료 + 배포 2026-04-12 21:35:11 +09:00
윤찬
8467ecbdb5 chore: project_status 갱신 — Phase V2-6 Admin CRM 고도화 완료 2026-04-12 21:33:17 +09:00
윤찬
dca1b90faa feat(admin): Phase V2-6 Admin CRM 고도화 — CRUD + 차트 + 결제 + 감사
- DB: audit_log 테이블(diff 포함) + subscriptions.admin_note + super_admin role
- Edge Functions 4개: admin-users, admin-subscriptions, admin-payments, admin-audit-log
- 공유 유틸: admin-auth.ts(권한 검증), audit.ts(감사로그 기록)
- Swagger UI: 독립 정적 페이지 + OpenAPI 3.0 spec
- CRUD 페이지: 구독 생성/수정/삭제, role 변경, 감사로그 목록/상세
- recharts: feature별 StackedBar + DAU Line + Top Users HorizontalBar
- 결제 이력: DB + Payple API 병행 조회
- 권한: super_admin만 위험 작업, admin은 조회 전용
- RLS: admin/super_admin IN 정책 + super_admin 쓰기 정책
- SQL RPC: admin_usage_by_feature, admin_top_users, admin_dau
2026-04-12 21:32:47 +09:00
윤찬
f7c50eb2ed docs: Phase V2-6 Admin CRM 고도화 설계서
Admin API(Edge Function) + Swagger UI + CRUD 웹페이지 +
recharts 차트 + 결제 이력 + 감사로그 설계.
2026-04-12 21:18:19 +09:00
윤찬
1439ed3c7a chore: Phase 3.3 세션 핸드오프 — SaaS 인프라 완결 2026-04-12 20:57:34 +09:00
윤찬
3ef971e0fd chore: project_status 갱신 — Admin CRM 독립 프로젝트 완료 2026-04-12 20:55:58 +09:00
윤찬
eb4c504fea fix(admin): RLS 재귀 수정 + OAuth 쿠키 + 코드 정리
- RLS: profiles 재귀 참조 → app_metadata 기반으로 교체
- auth callback: 쿠키를 response에 직접 설정 (세션 유지)
- middleware: Supabase 세션 갱신 추가
- layout: requireAdmin() 사용으로 통합 (중복 제거)
- admin-guard: app_metadata.role 기반 (JWT, DB 쿼리 불필요)
- debug 라우트 제거
- config.toml: localhost:3000/3001 redirect URL 추가
2026-04-12 20:52:56 +09:00
윤찬
46673ee941 refactor(admin): CRM을 apps/admin 독립 프로젝트로 분리
- apps/web에서 admin 라우트/가드/sidebar 링크 제거
- apps/admin: 독립 Next.js 앱 (포트 3001)
  - 자체 login/unauthorized/auth callback
  - admin-sidebar: Overview/Users/Subscriptions/Usage
  - requireAdmin() 가드: profile.role='admin' 체크
- monorepo workspace에 apps/admin 등록
2026-04-12 20:36:24 +09:00
윤찬
daed9f90d5 chore: project_status 갱신 — 마스터플랜 4개 작업 전체 완료 2026-04-12 20:26:46 +09:00
윤찬
7467bd6ce9 feat(desktop): Premium LLM SSE 스트리밍 — 토큰 단위 실시간 응답
- sse-parser.ts: Anthropic SSE 파서 (content_block_delta.text 추출)
- CloudSyncService.invokeFunctionStream(): raw fetch + SSE ReadableStream
- PremiumLLMService.chatStream(): stream=true, 토큰 단위 yield
- SSE 실패 시 자동 비스트리밍 fallback
- AbortController 연결 (취소 지원)
2026-04-12 20:26:17 +09:00
윤찬
667c09242b feat(web): 관리자 CRM 웹페이지 — admin role + 4개 페이지
- migration: profiles.role 컬럼 + admin RLS 정책
- admin-guard.ts: RSC용 admin 권한 체크
- /admin: CRM 대시보드 (총 유저, 유료 구독자, API 호출, 만료 예정)
- /admin/users: 유저 목록 (검색/필터/페이지네이션)
- /admin/users/[id]: 유저 상세 (프로필+구독+30일 사용량)
- /admin/subscriptions: 구독 목록 (상태 필터)
- /admin/usage: 사용량 집계 (7/14/30일)
- Sidebar에 Admin 네비게이션 추가
2026-04-12 20:24:26 +09:00
윤찬
d3c2a4348d feat(server): Payple 정기 갱신 크론 — payple-renew Edge Function + GitHub Actions
- payple-renew: 만료 구독 조회 → paypleBilling → 기간 갱신
- 3회 연속 실패 시 tier=free, status=expired 다운그레이드
- migration: subscriptions.renewal_failures 컬럼 추가
- payple-webhook: 결제 완료 시 renewal_failures=0 리셋
- GitHub Actions: 매일 01:00 UTC 스케줄
2026-04-12 20:18:11 +09:00
윤찬
36031acda9 refactor(desktop): LemonSqueezy API 코드 완전 제거 (~150줄)
- LicenseService: LS API 상수/인터페이스/메서드 4개 삭제
- activate(): 로컬 키 검증 전용으로 단순화
- deactivate(): LS API 호출 제거
- initialize(): _tryPeriodicValidation 제거
- site/ taxNote: LemonSqueezy → Payple (10 locale)
2026-04-12 20:15:40 +09:00
윤찬
1f2843c383 fix(i18n): 10개 locale에 구독 관련 키 13개 추가 (en fallback 누락 수정) 2026-04-12 19:56:01 +09:00
윤찬
1d6d9bded3 chore: project_status 갱신 — 라이선스 UI 빅뱅 완료 2026-04-12 19:54:25 +09:00
윤찬
996def683b refactor(desktop): 라이선스 UI 빅뱅 — 키 입력 제거, 구독 기반 전환
- LicenseTab/LicenseModal: LemonSqueezy 키 입력 삭제, Payple 구독 UI로 전환
- useLicenseState 훅: 라이선스+클라우드 인증 공용 상태 관리 추출
- PREMIUM_MODEL_LIMITS: 3곳 중복 → @d3ro/core/constants 단일 소스
- i18n: LemonSqueezy 전용 키 18개 삭제, 구독 관련 키 13개 추가 (12 locale)
- DashboardPage: dashboard.model* → license.model* 키 통일
2026-04-12 19:54:04 +09:00
윤찬
68881bf0d0 fix(desktop): Payple 배포 완료 + billing URL 환경분기 + 세션만료 버그 수정
- OPEN_BILLING: app.isPackaged 기반 dev/prod URL 분기
- CloudSyncService: stored token 없을 때 auth-changed null emit 누락 수정
- Payple Edge Functions 3개 배포 ACTIVE 확인
2026-04-12 19:31:02 +09:00
윤찬
ad058fbd4d chore: Phase 3.2-B wrap-up — project_status + handoff + 다음 세션 프롬프트 2026-04-12 19:19:55 +09:00
윤찬
c9baf031c9 feat(server+web+desktop): Phase 3.2-B Payple 결제 연동 + 대시보드 Premium 상태 표시
Payple PG 연동:
- _shared/payple.ts: Payple API 래퍼 (auth/billing/cancel/deleteBillingKey)
- payple-checkout Edge Function: 빌링키 결제 + 구독 활성화
- payple-webhook Edge Function: 결제완료/취소 이벤트
- payple-manage Edge Function: 구독 취소 (빌링키 해지)
- DB migration: payment_provider + payple_payer_id + payple_pay_oid
- 웹 billing 페이지: Payple JS SDK 결제창 + 관리 버튼 (Stripe 대체)
- Electron LicenseModal: shell.openExternal → 웹 결제 페이지

대시보드 Premium 상태:
- CrtDisplay services에 PREMIUM LLM LED 추가
- 백엔드 인디케이터 카드 (Local/Premium) + 티어 카드
- 사용량 섹션: 전 티어 표시 + 모델별 Premium 쿼터
- 12개 locale × 14개 i18n 키
2026-04-12 19:18:40 +09:00
윤찬
b8cb665264 chore: Phase 3.2 wrap-up — project_status + handoff + 다음 세션 프롬프트 2026-04-12 18:31:52 +09:00
윤찬
6e52c18e5b feat(desktop+server): Phase 3.2 Premium LLM — Anthropic Claude 프리미엄 파이프라인 + 모델별 쿼터 + SaaS UI
빅뱅 8/8 마지막 성공 기준 달성. Supabase Edge Function(llm-proxy)을 통해
Anthropic Claude를 호출하는 PremiumLLMService 신규 구현. 사용자가 Settings에서
Local/Premium 백엔드를 선택하면 VoiceModeService가 자동 분기하고, Premium 실패 시
Local로 silent fallback + 상단 중앙 배너 알림.

실측: Claude Haiku refine 1.6~3.2초 (이전 qwen3 42.9초 → 13~27배 빠름).

주요 변경:
- PremiumLLMService 신규 (싱글톤+EventEmitter, processText/chatStream,
  Supabase functions.invoke 기반, _ensureAuth 가드)
- llm-prompts.ts: SYSTEM_PROMPTS를 Local/Premium 공유 모듈로 추출
  (resolveSystemPrompt 헬퍼)
- VoiceModeService: _getLLMProcessor → _runProcessorWithFallback 라우터 +
  premium-llm-fallback 이벤트
- CloudSyncService: getAccessToken(async), getAnonKey, invokeFunction(auth
  헤더 자동 처리, 에러 body 파싱)
- IPC: LLM.PREMIUM_* 채널 6개 + preload API + llm-handlers 이벤트 전달
  (safeSendToRenderer 헬퍼)
- AppConfig.llmBackend: 'local' | 'premium' (기본 'local')
- Settings UI: Backend 드롭다운 + Premium 선택 시 Ollama UI 숨김 + 라이선스
  모달 자동 오픈
- AppLayout: 상단 중앙 Snackbar fallback 배너 (8초, warning filled)
- LicenseModal: 라이선스 키 입력 제거 → SaaS 구독 관리 UI 전환
  (Free/Pro/Pro+ 업그레이드 버튼, Payple 준비 중 스텁)
- 등급 비교 표: featureLabel i18n 번역 수정

서버 (Supabase Edge Functions):
- quota.ts: 모델별 쿼터 구조 (llm_haiku/sonnet/opus × free/pro/pro_plus),
  주간/일간 기간 분리, modelToQuotaKey 매핑, consumeQuota baseLimit 파라미터화
- llm-proxy: 모델별 쿼터 체크 + 소비 (checkQuota → consumeQuota 원자적),
  verify_jwt=false (2026 sb_publishable_ 키 호환)
- config.toml: llm-proxy verify_jwt = false
- migration 20260412000001: tier team→pro_plus 통일, subscriptions.overage_credits
  컬럼, consume_quota RPC (원자적 base→overage fallback)

Tier/쿼터:
- free: Haiku 250/주간, Sonnet/Opus 불가
- pro ₩9,900: Haiku 1500/일, Sonnet 300/일, Opus 50/일
- pro_plus ₩29,900: Haiku 무제한, Sonnet 1500/일, Opus 300/일
- api-client SubscriptionTier: team→pro_plus, overage_credits 필드 추가
2026-04-12 18:28:02 +09:00
윤찬
d397bcbf57 feat(desktop): LLM 기본 모델 qwen3:4b → gemma4:e4b 전면 전환 + think:false 안전장치
qwen3:4b가 reasoning 모델이라 <think>...</think> 블록을 길게 생성 →
stripReasoningBlocks 후 빈 문자열 → 원본 transcript fallback으로 끝나면서
LLM refine이 42초 걸리는 병목 발견. Google Gemma 4 e4b(4.5B effective
params, 2026-04-02 릴리스)로 교체. non-reasoning 기본 + Ollama v0.20+
think: false 파라미터로 2중 방어.

실측 결과: 받아쓰기 한 사이클 51.4s → 5.5s (9.3배 빠름).
  STT 500ms + LLM 3,925ms + insert 1,092ms.
refine 품질 정상 동작 확인: "테스트하는 중입니다" → "테스트하고 있습니다".

- LocalLLMService: 3개 fallback 기본값 변경(generate / streamGenerate /
  chatStream) + Ollama 요청 body에 think: false 명시 추가. non-reasoning
  모델은 무시, reasoning 모델은 thinking 토큰 차단. NO_THINK 주석을
  legacy 설명으로 업데이트 — qwen3/deepseek-r1 수동 선택자를 위한 3중
  방어(/no_think + think:false + stripReasoningBlocks) 명시.
- OnboardingModal / OllamaGuideModal: pull 명령어 갱신
- 테스트 fixture 갱신
- 12개 i18n locale JSON: settings.ollamaHint / ollama.step2.alt 키 업데이트
  (qwen3:4b → gemma4:e4b, qwen3:8b → gemma4:26b)
- 10개 site i18n locale TS + HowItWorks.tsx 파이프라인 시각화 — detail
  문자열 'qwen3 / llama3 / gemma3' → 'gemma4 / llama3.2 / phi4',
  파이프라인 라벨 'qwen3:4b @ localhost' → 'gemma4:e4b @ localhost'
- 설계서 00 LLMConfig 기본값 + CONFIG_DEFAULTS
- 설계서 05: 6개 API 스키마 예시, 2개 OllamaClient 코드 예시, LLM 모델
  추천 표 재정렬(gemma4:e4b 최상위, qwen3는 reasoning 경고와 함께 후순위),
  권장 JSON 설정에 think:false 추가
- phase-14 meeting mode 컨텍스트 윈도우 표 갱신
- V2-5 Mac 부트스트랩 가이드 pull 커맨드 갱신
- project_status.md Part 7 전체 섹션 추가
2026-04-12 09:47:08 +09:00
윤찬
a744551442 fix(desktop): Bug 13 전면 해소 — Conversation 에러 배너 + Dictation 빈 STT 가드 + recording tip 타이머 leak
- VoiceConversationPage: Snackbar + Alert(severity=warning filled) 하단 중앙
  배너 추가, onError 콜백에서 setErrorBanner. formatErrorMessage 헬퍼 —
  phase=stt + 'no speech' → i18n conversation.error.noSpeech, 그 외 →
  phaseLabel: rawMessage.
- VoiceModeService._transcribe: minBytes 가드(0.5s @ 16kHz 16bit mono =
  16000B) + 빈 result.text 가드. 양쪽 모두 _handleError(STTAudioTooShort /
  STTNoAudioData, 'No speech detected...')로 recording-tip popup error
  상태 3초 표시 재사용. 기존에는 빈 전사가 조용히 session completed +
  빈 history entry 생성되던 경로 차단.
- VoiceModeService._errorHideTimer 필드: _handleError의 setTimeout(hide, 3000)
  핸들 보관, _startSession 초기화 블록과 dispose()에서 clearTimeout. 다음
  세션 시작 후에도 이전 에러 타이머가 살아 진행 중인 recording tip을 ~2초
  지점에 숨기던 잠재 버그 수정 (실측 재현 및 해소 확인).
- i18n ko/en: conversation.error.phase.{stt,llm,tts} + conversation.error.noSpeech
  4개 키 추가.
2026-04-12 09:46:22 +09:00
윤찬
16aaf37956 chore: 빅뱅 Phase 5 Part 6 wrap-up — Voice Conversation 몰입 UX + U1/U3/U4/U7 전체 정리 + 다음 세션 프롬프트
이번 세션(Part 6) 산출물:
- 412a2e7: Voice Conversation 몰입 UX + 사운드 4개 + Bug 13 빈 STT (U6 + U8)
- dd591d0: AudioCaptureService Mac 분기 system_profiler (U7)
- dc7f933: CloudSync Realtime 재구독 백오프 + Refresh token 재인증 알림 (U1 + U3 + U4)

빅뱅 성공 기준 중 Phase 3.2 PREMIUM_LLM 하나만 남음. 선행 조건은 supabase
secrets set으로 ANTHROPIC/OPENAI 키 주입 (사용자 소유, 주입 후 다음 세션에서
완결). handoff-latest.md에 다음 세션 시작 프롬프트 A/B/C 분기 포함.
2026-04-11 23:27:25 +09:00
윤찬
dc7f93349e fix(desktop): CloudSync Realtime 자동 재구독 + Refresh token 실패 재인증 알림 (U1+U3, 빅뱅 Phase 5 Part 6)
## U1 — Realtime TIMED_OUT 자동 재구독

CloudSyncService.startRealtime()의 subscribe() 콜백이 TIMED_OUT 한 번만
로깅하고 끝나던 것을 지수 백오프 재구독으로 교체.
- SUBSCRIBED: retry count reset + timer clear
- TIMED_OUT / CHANNEL_ERROR / CLOSED: _scheduleRealtimeRetry() 트리거
- 백오프: 1s → 3s → 10s (MAX 3회)
- 초과 시 warn 로깅 후 포기 (Auto push fallback으로만 동작)
- stopRealtime()에서 타이머/카운터 cleanup

Publication, RLS, setAuth 모두 설정 완료 상태에서도 매 기동 시 TIMED_OUT이
찍히는 증상. 근본 원인은 Supabase 서버 transient(네트워크/WebSocket
타임아웃) 의심. 이 패치로 완전 해결은 아니지만 일시적 네트워크 jitter
복구 윈도우를 확보하고, 3회 실패 시 명확한 포기 메시지를 남긴다.

## U3 — Refresh token 실패 시 재인증 이벤트

init()의 refreshSession 실패 브랜치에서 warn 로깅 + 토큰 삭제만 수행해
사용자가 "왜 로그아웃됐지?" 상태로 방치되던 문제.
- logger.warn → logger.error(에러 레벨 승격)
- sync-error 이벤트 emit (사용자에게 보여줄 메시지 포함)
- auth-changed { user: null } emit (renderer 게이트 재잠금)
- catch 브랜치도 동일 처리 (기존엔 로깅만)

## U4 — package-lock.json optional dep (유지 결정)

rollup 전 플랫폼 optional deps가 lock에 기록돼 있지만 Windows 빌드자
사전 fetch 효율 + 크로스플랫폼 CI 환경을 고려해 유지. apps/desktop/
package.json:56에 @rollup/rollup-win32-x64-msvc 명시 의존이 있는 이유도
동일. 이 이슈는 확인 후 close.
2026-04-11 23:24:22 +09:00
윤찬
dd591d06e7 fix(desktop): AudioCaptureService.getDevices Mac 분기 — system_profiler CoreAudio 열거 (U7, 빅뱅 Phase 5 Part 6)
getDevices()가 powershell -NoProfile을 플랫폼 불문 호출해 Mac에서
"Failed to enumerate audio devices via PowerShell" 경고 + 1 device fallback이
발생하던 문제. process.platform 분기로 _getDevicesWindows / _getDevicesMac
helper로 분리하고, Mac은 system_profiler SPAudioDataType -json 을 파싱해
coreaudio_device_input > 0 인 항목만 input 디바이스로 추출.

macOS SoX 빌드는 실제 캡처 경로에서 -d(default) 고정이므로 deviceId는
'default'로 통일하고 label만 사용자 구분용으로 노출한다. Linux는 default
1개만 반환(pulseaudio/alsa 열거는 sox -d 로 충분).

Electron 재기동 후 로그: "Found 2 audio device(s) on darwin" 확인.
V2-5 플랫폼 감사 누락 항목 해소.
2026-04-11 23:18:18 +09:00
윤찬
412a2e71f9 feat(desktop): Voice Conversation 몰입 UX 패널 + 사운드 피드백 + Bug 13 빈 STT (빅뱅 Phase 5 Part 6)
listening 상태에서 풀 몰입 계측기 모드로 전환되는 VoiceRecordingPanel 추가.
recording-tip 팝업의 9바 cos-분포 waveform(BAR_COUNT=9, SMOOTHING=0.5,
RANDOM_FACTOR=0.35, 100ms)을 React로 포팅해 REC LED + elapsed 타이머 +
"SPEAK NOW" 힌트까지 구성. thinking/speaking 상태에서는 메시지 리스트로
복귀해 대화 맥락 유지 + 점 3개 typing indicator 버블 추가.

VoiceConversationService에 AudioCaptureService audio-level forwarding과
사운드 훅 4개(recording-start / recording-stop / chime / error)를 삽입.
chime은 recording-stop.wav 재사용(SoundEffectService SoundName 확장).
VOICE_CONVERSATION.AUDIO_LEVEL 채널 신설 + preload onAudioLevel API.

U8 Bug 13 동반 해소: finishListening에서 minBytes 미달 또는 VAD 무음 판정으로
빈 텍스트가 나오는 경우 조용히 listening으로 복귀하던 것을 _emitError('stt')로
사용자 피드백(에러 사운드 + 에러 이벤트)을 노출하도록 수정. 사용자가 "⏹ 눌러도
반응 없음"으로 오해하던 증상 해소.
2026-04-11 23:16:34 +09:00
윤찬
e420e35ade chore: handoff 다음 세션 프롬프트에 dev 자동 기동 첫 액션 추가 (사용자 고정 요구사항) 2026-04-11 23:01:38 +09:00
윤찬
e328eb1117 chore: 빅뱅 Phase 5 Part 5 wrap-up — /simplify + Voice Conversation 파이프라인 복구 + UX 설계 이월
이번 세션 범위:
- Part 5-A: /simplify 패스 — isImeComposingEvent helper 추출, 8곳 IME 가드 통합 (커밋 3b77be0)
- Part 5-B: Voice Conversation 파이프라인 복구 — Bug 10.5/11/12 3개 버그 연쇄 fix (커밋 ee6ac3d)
- Part 5-C: Voice Conversation UX 개선 인터뷰 설계 — 몰입 패널 + 사운드 피드백 + 상태 분기
  (ASCII 와이어프레임 + 구현 작업 리스트 handoff에 저장, 다음 세션 이월)
- Part 5-D: Bug 13 신규 발견 — 빈 STT 결과 시 피드백 부재 (U6 몰입 패널과 함께 처리 예정)

갱신 파일:
- memory/project_status.md — SaaS [13] 엔트리 (Bug 10.5/11/12/13, UX 설계 스펙)
- memory/handoff-latest.md — Part 5 전체 요약 + Part 5-C 와이어프레임 + 다음 세션 프롬프트
- memory/handoff-archive/2026-04-11-2200-phase5-part5a-simplify.md — 직전 Part 5-A 스냅샷 아카이브

다음 세션 우선 작업:
1. Voice Conversation 몰입 패널 구현 (handoff의 Part 5-C 작업 리스트 8번까지)
2. Bug 13 피드백 훅 추가 (몰입 패널과 함께)
3. Phase 3.2 PREMIUM_LLM (선행: Anthropic/OpenAI API key 주입)

빅뱅 성공 기준: 8개 중 7개 완료 (Phase 3.2 PREMIUM_LLM만 잔존).
2026-04-11 22:59:26 +09:00
윤찬
ee6ac3dbfc fix(desktop): Voice Conversation Mac 파이프라인 복구 — TTS 플랫폼 분기 + finishListening 스냅샷 순서 + Whisper 사전 로드 (빅뱅 Phase 5 Part 5)
Voice Conversation이 Mac에서 한 번도 end-to-end 동작한 적 없었음을 발견. 3개 독립 버그가
중첩돼 있었고, 진단 로그를 추가해 단계적으로 분해하여 모두 해결.

Bug 10.5 — TTSPlaybackService 'spawn powershell ENOENT' (Mac)
- 원인: _speakOne이 spawn('powershell', ...) 하드코딩. 파일 주석부터 "Windows SAPI (PowerShell)
  기반". V2-5 플랫폼 감사 때 이 파일 누락.
- Fix: _speakOne을 platform dispatcher로 변경, _speakOneMac(/usr/bin/say -r <wpm> --) +
  _speakOneWindows(기존 PowerShell) 분리. 공통 close/error 핸들러는 _bindProcessHandlers로
  추출. rate 매핑은 _getMacRate(180*speed, WPM 기준) / _getWindowsRate(-10~10, SAPI) 분리.
- 파급: 음성 대화뿐 아니라 TTS를 쓰는 모든 경로가 Mac에서 벙어리였음.

Bug 11 — VoiceConversationService.finishListening 오디오 버퍼 스냅샷 순서 (진짜 root cause)
- 증상: finishListening 호출 시 audioBuffer가 항상 0 bytes → "audio too short"로 조용히
  listening 복귀. STT가 한 번도 안 탐. Windows에서도 동일 버그였을 가능성 매우 높음.
- 원인: _stopListening() 내부에 this._audioBuffers = []로 리셋하는 라인이 있는데,
  finishListening이 concat을 stop 호출 **이후**에 수행 → 이미 빈 배열에서 concat.
  19초짜리 1.7MB 오디오가 매번 증발.
- Fix: _stopListening() 호출 **전**에 const audioBuffer = Buffer.concat(this._audioBuffers)
  스냅샷 저장. finishListening 내 redundant한 this._audioBuffers = [] 재설정도 제거(이미
  _stopListening이 수행).

Bug 12 — Whisper 모델 사전 로드 누락
- 증상: Bug 11 fix 후 STT 경로로는 진입하지만 LocalSTTService가 "모델 로딩 중, 오디오 버퍼에
  적재"로 pending 큐에 쌓기만 하고 아무도 로드를 트리거하지 않아 영원히 대기.
- 원인: MeetingMode/CaptionService는 startRecording 시 LocalSTTService.initialize()를 명시적으로
  호출하지만 VoiceConversationService는 이 호출 누락.
- Fix: startSession에서 void getLocalSTTService().initialize().catch(...) fire-and-forget.
  initialize는 같은 모델이 Ready면 즉시 return하므로 idempotent.

검증:
- desktop tsc --noEmit EXIT=0
- /usr/bin/say -r 180 "TTS 분기 테스트 성공" 직접 호출 → 스피커 소리 확인
- 사용자 재시연: Mic → 말함 → ⏹ Stop → 전사 → LLM 스트리밍 → TTS 소리 → 자동 listening
  복귀 연속 대화까지 end-to-end 통과

남은 과제 (다음 세션 U6/U8):
- Voice Conversation UX 몰입 패널 (9바 waveform + REC LED + 타이머) — 설계 완료
- Bug 13: 빈 STT 결과 시 사용자 피드백 부재 (현재는 조용히 listening 복귀 → 버튼 먹통처럼 보임)
2026-04-11 22:59:08 +09:00
윤찬
3b77be01bc refactor(desktop): IME composition 가드 helper 추출 + 8곳 Enter 핸들러 통합 (빅뱅 Phase 5 Part 5)
Bug 10 fix(MeetingModePage 인라인 가드)를 isImeComposingEvent helper로 추출하고,
한글 위험도 있는 나머지 7개 Enter 핸들러에 일괄 적용. 총 8곳이 이제 동일 helper 경유.

신규:
- apps/desktop/src/renderer/utils/keyboard.ts — isImeComposingEvent(e)
  JSDoc에 Bug 10 원리(Chromium이 IME 조합 중 Enter를 2번 발화) + 권장 사용 패턴 포함

적용 8곳:
- pages/MeetingModePage.tsx:164 회의 메모 (기존 인라인 가드 4줄 교체)
- pages/KnowledgeBasePage.tsx:84 RAG 쿼리
- pages/VoiceConversationPage.tsx:113 텍스트 채팅
- pages/CommandsPage.tsx:336 키워드 추가 (Enter+Esc)
- components/meeting/MeetingChatPanel.tsx:111 미팅 챗
- components/meeting/EditableSegment.tsx:70 전사 세그먼트 편집 (Enter+Esc)
- components/meeting/MeetingDetailTabs.tsx:242 미팅 타이틀 (인라인 arrow → 블록)
- components/shared/HistoryEntryCard.tsx:64 태그 추가 (Enter+Esc)

/simplify 패스 품질 리뷰:
- Phase 3.3 CloudSyncService.pushOne 훅 8곳은 이미 fire-and-forget 1줄로 일관.
  내부 try-catch가 에러 삼켜 로컬 write 차단 금지 철학 준수 → 수정 없음, 현 상태가 최적.

검증:
- desktop tsc --noEmit EXIT=0
- Vite HMR로 dev 프로세스 자동 반영 (재기동 없음)
- 한글 Enter 시연은 사용자 실측 대기 (VoiceConversationPage / MeetingDetailTabs 대표 2곳)
2026-04-11 22:06:16 +09:00
윤찬
c0c6206cbe chore: 빅뱅 Phase 5 Part 4 wrap-up — Meeting pre-push 직접 실증 완료 + 다음 세션 프롬프트
- handoff-archive에 Part 3(2026-04-11 21:40) 스냅샷 보존
- handoff-latest를 Part 4 내용으로 전면 갱신
  - 실증 타임라인(session 78421e75) 전체
  - Bug 10 원인/수정/검증 요약
  - 잠재 IME 버그 7곳 목록 (다음 /simplify에서 일괄 처리)
  - 미해결 이슈 U1/U3/U4 + 신규 U5
  - 다음 세션 시작 프롬프트: /simplify 패스 + IME 가드 7곳 일괄 추가

다음 세션 첫 액션:
1. /simplify 패스 — Phase 3.3 + Bug 10 fix 품질 리뷰
2. IME 가드 7곳 일괄 추가
3. Phase 3.2 PREMIUM_LLM (API key 주입 대기)
2026-04-11 21:58:32 +09:00
윤찬
96e23f57bc fix(desktop): 한글 IME Enter 중복 addMemo 가드 + Meeting pre-push 직접 실증 (빅뱅 Phase 5 Part 4)
Phase 5 Part 3에서 남겨둔 U2(Fix 1 직접 시연 미수행)를 해소하고,
실증 중 발견한 한글 IME Enter 중복 addMemo 버그(Bug 10)를 같이 픽스.

## Bug 10 — 한글 IME Enter 중복 addMemo
- 증상: 실증 녹음 중 한글 메모 "메모 기능이 잘 작동하는지 봅니다"가
  2ms 간격으로 2번 저장 (로그 21:47:57.410/.412, UUID c7f0db45 + a6e442cc).
  5개 메모 중 이 하나만 중복 — 조합이 아직 확정 안 된 상태에서 Enter를
  친 메모에서만 발생.
- 원인: 한글 IME 조합 중 Enter → Chromium이 keydown 2번 발화
  ((1) IME 조합 확정 isComposing=true/keyCode=229,
   (2) 실제 Enter 액션). MeetingModePage.tsx:164-174의 handleMemoKeyDown이
  e.key === 'Enter' && !e.shiftKey만 검사하고 isComposing을 무시해
  두 이벤트에서 모두 handleAddMemo() 발화 → IPC 2회 → addMemo() 2회.
- 수정: MeetingModePage.tsx:168에 이중 가드 추가.
  isComposing은 모던 표준, keyCode === 229는 조합 확정 시점에
  isComposing이 false로 내려가면서 keyCode만 229로 주는 Chromium
  엣지 케이스 호환.
- 검증: Vite HMR로 즉시 반영 후 재시연 녹음(78421e75)에서 한글 메모
  5건 연속 → 전원 1:1 매핑, 중복 0.

## Fix 1 (Phase 3.3 startRecording pre-push) 직접 실증
재시연 녹음 78421e75 (21:51:24~21:51:50, 약 26초) 로그 타임라인:

  21:51:24.937  pushOne meetings/78421e75 ok      <- Fix 1 pre-push (676ms)
  21:51:30.499  pushOne meeting_memos/4ed7e59a ok <- addMemo #1 (113ms)
  21:51:32.202  pushOne meeting_memos/de011c15 ok <- addMemo #2 (78ms)
  21:51:35.893  pushOne meeting_memos/3184fe5e ok <- addMemo #3 (66ms)
  21:51:38.263  pushOne meeting_memos/00f041ef ok <- addMemo #4 (82ms)
  21:51:40.740  pushOne meeting_memos/0808c4d2 ok <- addMemo #5 (64ms)
  21:51:50.681  pushOne meetings/78421e75 ok      <- _runPostProcessing (108ms)
  21:51:50.709  pushOne history/70388090 ok       <- CaptionService→History

meetings/78421e75가 녹음 시작 + 종료 두 번 push된 것이
Fix 1의 직접 증거 — 부모 row가 메모 push 시점에 이미 Supabase에
존재했기 때문에 메모 5건이 RLS/FK 차단 없이 통과. Bug 7 재현 방지 확인.

## 잠재 IME 버그 7곳
동일 패턴의 Enter 핸들러 7곳에 가드 없음 (KnowledgeBasePage:85,
VoiceConversationPage:114, MeetingChatPanel:113, EditableSegment:72,
MeetingDetailTabs:242, HistoryEntryCard:65, CommandsPage:337).
다음 세션 /simplify 패스에서 일괄 처리 예정.

## 검증
- desktop tsc --noEmit EXIT=0
- Meeting 녹음 2회차 실증 통과

memory/project_status.md SaaS [11] 섹션 추가.
2026-04-11 21:58:20 +09:00
윤찬
2a8efa81af chore: 빅뱅 Phase 5 Part 3 wrap-up — Phase 3.3 Auto push 완료 + 다음 세션 프롬프트
- handoff-latest.md: Part 3 완료 스냅샷 (Bug 7/8/9 픽스 경위, 미해결 4건 U1~U4)
- 이전 Part 2 핸드오프를 handoff-archive/로 보존
- 다음 세션 첫 액션: Meeting 모드 pre-push 직접 실증 (Fix 1의 직접 증거)
2026-04-11 21:38:16 +09:00
윤찬
046ac857cb feat(desktop+web): Phase 3.3 Auto push on write + transcript fallback + dashboard Link (빅뱅 Phase 5 Part 3)
Desktop — CloudSyncService:
- pushOne(table, id) 제네릭 단건 push 신규 (history/dictionary/meetings/meeting_memos/meeting_documents)
- 로그인 미상태 silent noop, 실패 시 warn만 (로컬 write 절대 차단 금지)
- 5개 row→payload 매퍼(_mapHistoryPayload 외 4개) private 메서드로 추출, pushAll과 공유 (DRY)

Desktop — write hook 8곳 fire-and-forget 연결:
- HistoryService.create / generateTitle
- MeetingSummaryService.generateSummary (summaryText 저장 후)
- DictionaryService.add / update
- MeetingModeService.startRecording (부모 meeting pre-push — 자식 memo/doc RLS 통과용)
- MeetingModeService.addMemo / generateDocument
- MeetingModeService._runPostProcessing (완료 상태 최종 저장 후)

Web — meeting detail page:
- transcripts 테이블이 비고 status!=recording인 경우 meeting.raw_transcript(또는 edited_transcript) 를 whiteSpace:pre-wrap으로 fallback 렌더
- 실시간 구독 경로(LiveTranscriptList)는 recording 상태 또는 transcripts 존재 시에만 마운트

Web — dashboard:
- RECENT MEETINGS의 MetalCard를 next/link Link로 감싸 /meetings/[id] 이동 가능
- cursor pointer + hover lift 효과

실증: 재기동 직후 pushAll 캐치업이 이전 세션 RLS 실패 메모 2건을 재시도 → pushed=4 성공.
새 녹음 → pushOne history/... ok 실시간 발화 → 웹에서 전사/메모 렌더 확인 (사용자 "잘 전사와 메모가 올라왔어")
2026-04-11 21:34:02 +09:00
윤찬
4e6280bc69 chore: 빅뱅 Phase 5 Part 2 wrap-up — 실증 A~E 전원 통과 + 다음 세션 프롬프트
SaaS [9] 갱신 + handoff-latest 재작성.

## 이 세션 성과
- Bug 4/5/6 구조 픽스 (nanoid→UUID, realtime publication, setAuth 명시)
- 실증 A/B/C/D/E 전원 통과 — 3 클라이언트 데이터 일원화 end-to-end 검증 완료
- 로컬→클라우드 push 최초 성공 (실증 B pushed=1, 실증 E pushed=5)
- 웹 Dashboard에 데스크톱 미팅 row 반영 스크린샷 캡처 (RECENT MEETINGS "(제목 없음) 2026.4.11 오후 7:09:21")
- 빅뱅 성공 기준 8개 중 7개 완료 (PREMIUM_LLM만 미구현)

## 미해결 (다음 세션 이월)
- Phase 3.3 Auto push on write — 수동 Push 버튼 대신 세션 완료 즉시 자동 push
- U1: Realtime 채널 TIMED_OUT 근본 원인 (블로커 아님, Bug 5+6 적용 후에도 재현)
- U2: 웹 Dashboard Recent Meetings 카드 클릭 → 상세 이동 안 됨 (사용자 피드백, 해석 확인 필요)
- U3: package-lock.json 정리 지연 (rollup-win32 optional dep로 npm install Mac 실패)

다음 세션 시작 프롬프트는 memory/handoff-latest.md 말미에 그대로 붙여넣기 가능.
아카이브: 이전 handoff는 memory/handoff-archive/2026-04-11-1800-phase5-part1-wrap.md로 이동.
2026-04-11 19:16:50 +09:00