# D3RO-VOICE 프로젝트 현황 > 마지막 갱신: 2026-07-21 (Whisper large-v3-turbo 전환 + 온보딩 2단계 부트스트랩) ## 모델 현대화 트랙 (2026-07-21) — B 완료, A 완료(코드), C 완료(코드) ### 방향 결정 (사용자 컨펌) - **A+B 병행**: B(로컬 Whisper turbo 전환 + 부트스트랩 진행률 UI) 먼저 → A(OpenAI gpt-realtime-2.1 라이브 음성 대화 Premium 백엔드) - **C (방향 반전 확인됨)**: agent-switchboard-client의 릴리스 체계를 **D3RO로 이식** (원래 지시가 반대로 전달됐었음 — 대상은 이미 우수한 파이프라인 보유). Mac 배포 + Release 자동화 + 자동 업데이트. ### C 완료(코드): switchboard 릴리스 체계 이식 ✅ - **package-macos CI job**: arm64 무서명(ad-hoc, `identity=-` + hardenedRuntime=false + CSC_IDENTITY_AUTO_DISCOVERY=false — switchboard 검증 조합). 사이드카 venv PyInstaller + brew sox 번들 + Ollama darwin. `D3RO_MAC_RUNNER="true"` CI 변수 게이트 + allow_failure(mac 실패 시 Windows 단독 릴리스). - **sync-version.mjs**: 태그 → apps/desktop/package.json 버전 동기화 (기존 "Setup 1.0.0.exe" 불일치 해소) - **publish-gitlab-release.mjs**: 산출물을 Generic Package Registry 버전별+latest 업로드 + GitLab Release asset 링크 (기존 PowerShell curl 잡 대체). latest.yml 필수 검증, latest 삭제는 best-effort. - **electron-builder.yml**: `publish: generic` 추가(latest.yml 생성 트리거 — 없으면 update info 미생성!), 공백 없는 artifactName(`D3RO-Voice-Setup-*`), mac arm64 단일화(x64 사이드카 불가) - **UpdateService** (신규, 싱글톤+EventEmitter): electron-updater generic provider, 4h 주기, 다운로드 완료 시 재시작 다이얼로그(mainWindow parent). `update-feed.ts` SSOT — URL 미기입 시 비활성. Windows 전용(mac 무서명은 Squirrel.Mac 제약). - **의존성**: electron-updater 추가, `@rollup/rollup-win32-x64-msvc`를 optionalDependencies로 이동(mac npm ci EBADPLATFORM 해소), lockfile에서 workspace 밖 RN 의존성 163개 정리 - **가이드**: `docs/deployment/release-guide.md` — mac runner 등록, feed URL 2곳 SSOT, 파일명 규칙, 후속(서명/공증/Ed25519) 정리 - **검증**: ultracode 리뷰 워크플로(16 에이전트, 3관점 → 적대적 검증)로 결함 8건(중복 제외) 확정 → 전부 수정. tsc 0 / vitest 41 / YAML·mjs OK. ### D 완료: v0.1.1-alpha 실배포 성공 (2026-07-21) ✅ - **runner 인프라 변화 발견**: YUNCHAN-1 소멸 → 사내 공용 TW-BUILD01(linux docker/shell), TW-VIVEN-BUILD(build-win-x64), TW-BUILD-MAC-ARM64(build-mac-arm64). CI 태그 전면 교체. - **사전 설정 완료(API)**: 프로젝트 ID **1172** → feed URL 2곳 기입, `D3RO_MAC_RUNNER=true` 변수, `package_registry_access_level=public` - **1차 파이프라인(#69730) 실패 → 수정 → 2차(#69733) 성공**: - 원인 1: electron-builder가 prerelease에서 채널 alpha 감지 → `alpha.yml` 생성 → `detectUpdateChannel: false`로 latest 고정 - 원인 2: mac runner에 sox/brew 없음 (allow_failure로 릴리스는 진행) - Windows 빌드 11분 (VIVEN runner, signtool 서명까지 됨 — 머신 전역 인증서) - **Release v0.1.1-alpha**: `D3RO-Voice-Setup-0.1.1-alpha-x64.exe`(735MB) + latest.yml + blockmap이 Generic Package Registry 버전별+latest에 게시, Release asset 링크 생성 - **E2E 검증 완료**: 익명 다운로드 200 OK → 사일런트 설치(`%LOCALAPPDATA%\Programs\@d3rodesktop\D3RO Voice.exe`) → 앱 기동 → 로그에서 전체 bootstrap 정상 + **UpdateService가 feed 체크 후 "0.1.1-alpha is not available (latest)" 정확 판정** — 자동 업데이트 루프 프로덕션 검증됨 - mac: PATH 수정(brew/volta) 커밋 후 main 파이프라인으로 검증 중. 사이드카 arm64 PyInstaller 빌드는 이미 성공 확인(183MB). ### E 완료: v0.1.2-alpha — Mac 포함 첫 멀티플랫폼 릴리스 + 자동 업데이트 라이브 검증 ✅ - mac 빌드 수정 2건: ①brew/volta PATH 명시(CI 셸은 비로그인), ②Ollama darwin 자산명(`ollama-darwin` 단일 바이너리, tgz 아님) - **파이프라인 #69743 전 잡 그린**: Windows exe + **Mac dmg/zip(arm64, ad-hoc 서명)** + latest.yml/latest-mac.yml 전부 Release 게시 - **자동 업데이트 실전 검증**: 실행 중이던 0.1.1-alpha 앱 재시작 → 30초 후 feed 체크 → "Found version 0.1.2-alpha" → 735MB 다운로드 7초(사내망) → 재시작 다이얼로그 표시. 전체 루프 완주. - 알려진 사소 이슈: 차등(blockmap) 다운로드 시 이전 버전 blockmap URL에서 :8443 포트가 탈락해 404 → full download 폴백으로 동작 (electron-updater 비표준 포트 quirk, 비차단) ### F: 온보딩 gemma4 pull 412 실패 → Ollama 번들 v0.32.1 범프 (v0.1.3-alpha) - **증상**: 설치 앱 온보딩에서 "다운로드 실패" — Ollama 레지스트리가 412 반환 ("requires a newer version of Ollama") - **원인**: 번들 Ollama v0.5.7 ≪ gemma4:e4b 요구 0.20+. 4/11 모델 전환과 4/15 번들 결정이 서로 버전 정합 안 됨. 개발 환경은 시스템 Ollama 0.20.5가 가려줌 (번들 우선은 패키지 앱에서만 발동) - **수정**: OLLAMA_VERSION v0.32.1(최신), 다운로드 스크립트 2종에 `.ollama-version` 스탬프 캐시 무효화(runner가 checkout 재사용), cuda*/rocm* 재귀 가지치기(신구 zip 레이아웃 모두), darwin은 v0.6+부터 tgz 복귀(bin/ollama·루트 모두 지원), 1.5GB 초과 경고 - 배포 경로: v0.1.3-alpha 태그 → 설치 앱이 자동 업데이트로 수신 ✅ - **v0.1.3-alpha 릴리스 완료** (파이프라인 #69747 전 잡 그린): Ollama v0.32.1 CPU 전용 가지치기 후 **119MB** (기존 ~1GB), 설치파일 735MB → **225MB** - **차등 다운로드도 이번엔 동작**: 0.1.2→0.1.3 델타 27MB만 다운로드(12%) — updater 캐시의 current.blockmap 사용. 이전 404는 첫 업데이트라 old blockmap이 없어서였음 - **v0.1.3 실측 결과 (2026-07-21 17:55~17:58)**: 온보딩 전체 성공 — gemma4:e4b pull 완료(레이어가 로컬 캐시에 있어 검증만 ~2분), Whisper `medium` 1.5GB 다운로드 25초, onboardingCompleted 저장. 사용자는 "조용한 실패"로 인지 → 원인은 **verifying(무진행) 구간 ~2분간 UI 피드백 부재** (UX 버그, 기능 정상) - **G: 온보딩 UX 수정 (v0.1.4-alpha) — 릴리스 완료(#69756 전 잡 그린), 차등 24MB로 설치 앱 전달 확인**: ①total 없는 상태(pulling/verifying/writing manifest)에서 인디터미넌트 바 + 친화적 문구(i18n 3키), ②중복 클릭 가드(runningRef), ③LocalLLMService.pullModel 동시 호출 dedupe(in-flight map), ④STT /download 409 시 기존 진행 폴링 합류 - 참고: 온보딩 STT 단계는 사용자의 기존 설정 모델(medium)을 존중해 다운로드함 — turbo 강제 아님 (설계 의도) ### H: 앱 아이콘 + 트레이 + 마이크 테스트 버튼 수정 (v0.1.5-alpha) — 릴리스+전달 완료 ✅ (#69759 전 잡 그린, 설치 앱 0.1.5.70 확인) - **앱 아이콘 신규**: build/icon.svg(마스터, d3roPalette SSOT — 메탈 섀시+다크 글래스+오렌지 웨이브 5바 cos 분포) → icon.png(1024)+icon.ico(6사이즈). `scripts/generate-icons.mjs`(sharp, ico 컨테이너 직접 조립). electron-builder win.icon/mac.icon 연결 + extraResources icons/ - **트레이 아이콘 버그**: createEmpty()로 빈 이미지였음 → getAppIconPath()(paths.ts, win=ico/기타=png, dev는 build/) 사용 - **마이크 테스트 버튼 조기 종료 버그** (mic-test-debug 에이전트 진단): 렌더러가 초기 무음 `level:0`을 종료 신호로 오인 → 즉시 구독 해제. 수정: TEST_LEVEL 이벤트에 `done` 플래그(SSOT 채널 `AUDIO.TEST_LEVEL`/`STOP_TEST` 신설, 하드코딩 'audio:testLevel' 제거), 렌더러는 done으로만 종료, STOP 버튼이 실제로 메인 캡처 중지, testDevice 실패 시 상태 롤백 - 잔여(경미): 설계서 02의 audio:testDevice 명세는 "짧은 캡처 후 결과 반환"인데 실제는 스트리밍 — 명세 갱신 후보. AudioCaptureService.testCapture() dead code 정리 후보 #### 남은 것 1. 설치 디렉토리명 `@d3rodesktop` 정리 (electron-builder가 package name에서 유도 — 다음 릴리스에서 명시) 2. VIVEN runner의 signtool 서명이 어떤 인증서인지 확인 (twentyoz 조직 인증서면 유지) 3. Mac dmg 실기기 설치 테스트 (Gatekeeper 우회: 우클릭→열기) 4. blockmap 차등 다운로드 포트 탈락 quirk 조사 (선택) 5. 다음 릴리스부터는 태그 푸시만 하면 끝 ### B 완료: Whisper large-v3-turbo 전환 + 온보딩 2단계 부트스트랩 ✅ - **기본 STT 모델**: `base` → `large-v3-turbo` (CONFIG_DEFAULTS). 6배 빠름, 정확도 손실 1~2%, 1.6GB - **사이드카 다운로드 API**: `POST /download`(백그라운드 스레드, HF 파일 스트리밍, 멱등), `GET /download/status`, `POST /download/cancel`, `--models-dir` 인자 - 저장 위치: `userData/whisper-models//` — `/load`가 로컬 디렉토리 우선, 없으면 HF 자동DL 폴백 - requirements: faster-whisper>=1.1.0 (turbo 지원), huggingface_hub/requests 명시 - **LocalSTTService**: `downloadModel()`(폴링 500ms + download-progress emit), `cancelDownload()`, getModels()가 fs로 downloaded 판정 - **IPC**: 설계서 02에 이미 정의돼 있던 `stt:downloadModel`/`stt:cancelDownload`/`stt:downloadProgress` 채널 구현 완료 (기존 미구현) - **OnboardingModal 2단계**: LLM(gemma4:e4b) → STT(large-v3-turbo) 순차 다운로드, 단계 표시 + MB 진행률, 필요한 단계만 실행(멱등 재시도) - **설정**: SettingsModal에 large-v3-turbo 선택지, `settings.model.largeTurbo` 12 locale - **i18n**: `onboarding.sttModelMissing/sttModelSize/step/progressDetail` (ko/en) - **설계서 동기화**: 00(STTConfig union + 기본값), 01(카탈로그 주석), 05(/load 주석) - **테스트 수리**: errors.test.ts / VoiceModeService.test.ts의 모노레포 전환 잔재 import(`src/shared/*` → `@d3ro/core/*`) 수정 → 41/41 통과 - 검증: desktop tsc EXIT 0, vitest 41 passed ### A 구현 완료(코드): gpt-realtime-2.1 라이브 음성 대화 (Premium) ✅ - 2026-05 출시 OpenAI Realtime 계열: gpt-realtime-2/2.1(~$0.05/분), 2.1-mini(~$0.016/분), GPT-Realtime-Whisper($0.017/분), GPT-Realtime-Translate($0.034/분) - **서버**: `realtime-token` Edge Function 신규 — requireUser → 티어 검증(free 차단, pro=2.1-mini, pro_plus=2.1) → `realtime_session` 쿼터(일간 pro 30/pro+ 120 세션) 소비 → OpenAI `POST /v1/realtime/client_secrets` → ephemeral key 반환. quota.ts에 `realtime_session` 키 추가. config.toml verify_jwt=false. - **IPC**: `voiceConversation:getRealtimeToken` (main이 CloudSync invokeFunction 경유), ErrorCode 799 `ConversationRealtimeTokenFailed` - **렌더러**: `useRealtimeConversation` 훅 — getUserMedia → RTCPeerConnection → SDP를 `POST api.openai.com/v1/realtime/calls`(Bearer ek_) → data channel 'oai-events' 이벤트 매핑(response.created=thinking, *audio_transcript.delta=speaking+스트리밍, input_audio_transcription.completed=유저 메시지, response.done=확정+listening). dc open 시 session.update로 whisper-1 입력 전사 활성화(실패해도 대화 유지). - **VoiceConversationPage**: `conversationBackend` 설정 분기 — realtime이면 훅 사용, 연결 실패 시 로컬 파이프라인 자동 fallback + 배너. LIVE 배지, 텍스트 입력 상시 표시. - **설정**: AppConfig.conversationBackend('local'|'realtime', 기본 local), SettingsModal LLM 탭에 "음성 대화 엔진" 선택 + realtime 선택 시 라이선스 모달 - **i18n**: settings.convBackend.* 5키 + conversation.realtime.* 3키 (ko/en, 나머지는 en fallback) - 검증: desktop tsc EXIT 0, vitest 41/41. deno check의 @ts-expect-error 에러는 기존 llm-proxy와 동일(관행) #### A 남은 사용자 액션 (배포/E2E) 1. `supabase secrets set OPENAI_API_KEY=sk-...` 2. `supabase functions deploy realtime-token` 3. Pro 계정으로 로그인 → 설정 → 음성 대화 엔진 = Live → 대화 페이지에서 마이크 시작 → 실측 4. (선택) 이벤트명이 2.1 API와 다르면 훅의 handleServerEvent 케이스 보강 5. (후속) 분 단위 정밀 쿼터 (세션 수 → 분 집계), settings.convBackend i18n 10개 locale 확장 ## v0.1.0-alpha 배포 전체 흐름 (2026-04-15 night) ✅ ### GitLab CI 파이프라인 확립 - `d3ro-voice-win-build` runner 등록(YUNCHAN-1 PC, shell/powershell executor, tags: windows+shell) - `config.toml`에 machine PATH 고정 삽입(`C:\Program Files\Python311;...`) → Windows Store Python stub 우회 - Python 3.11.9 공식 설치(machine-wide) - 파이프라인 성공 순서: `npm ci → pip install → build-sidecar.py(PyInstaller 232MB) → download-sox.ps1 → download-ollama.ps1 → electron-vite build → electron-builder NSIS` ### 해결된 빌드 이슈 타임라인 1. Python 미감지 → machine-wide 설치 + runner config.toml의 environment로 PATH 주입 2. SoX SourceForge 다운로드 차단 → 바이너리 직접 커밋(`apps/desktop/resources/sox/sox.exe` + DLL들) 3. Ollama 다운로드 한글 인코딩 파싱 실패 → `download-ollama.ps1` 메시지 전부 영문화 4. Invoke-WebRequest 대용량(~2GB) hang → `curl.exe` + 30분 타임아웃 + 진행률 바 5. `package.json`의 `build.configFile` electron-builder v26에서 미지원 → `--config` 플래그로 전환 6. 모노레포 hoisting → electron 자동감지 실패 → `electronVersion: "33.3.0"` 명시 7. 아이콘 빈 폴더 자동 스캔 실패 → `directories.buildResources: build/`로 변경, `win/mac/linux.icon` 제거 8. GitLab artifacts 413 Too Large → `release/*/*.exe` + `latest.yml` + blockmap만 업로드 9. `expose_as`와 와일드카드 path 병행 불가 → `expose_as` 제거 ### 해결된 설치러너 크래시 - **증상**: `D3RO Voice Setup 1.0.0.exe` 실행 직후 `System.dll` 0xc0000005 액세스 위반 - **근본 원인**: 설치 파일이 NSIS 2GB 한도 초과(Ollama 전체 ~2GB) - **해결 3종**: 1. `download-ollama.ps1`에서 GPU 런타임(cuda_v11/cuda_v12/rocm_avx) 가지치기 → ~1GB 절감 2. `nsis.oneClick: true` + `allowToChangeInstallationDirectory: false` → electron-builder issue #7921 회피 3. `installer.nsh` 커스텀 include 비활성화(커스텀 `ReadRegDWORD`/`MessageBox`는 v0.1 이후 재검토) ### 온보딩 모달 버그(비크래시) - `{model}`, `{status}`, `{message}` 플레이스홀더가 화면에 그대로 노출 - 원인: `@d3ro/i18n`의 `resolveTranslation`은 `{{var}}`(이중 중괄호) 포맷 요구 - 다음 태그 빌드부터 반영 (수정 완료, main push 완료) ### 현재 상태 - 설치파일 설치 성공 - 앱 기동 성공 - 온보딩 모달(gemma4:e4b 미설치 감지) 표시 확인 - **다음 검증**: 다운로드 시작 → 진행률 UI → 전사 E2E ### 다음 작업 1. 현재 설치 앱으로 "다운로드 시작" 누르고 gemma4:e4b pull 동작 확인 2. Alt+Shift+1 단축키로 전사 E2E 테스트 (SoX + 번들 sidecar + Whisper large-v3 자동 DL) 3. i18n 플레이스홀더 수정본 재배포용 v0.1.0-alpha.1 태그 (선택) 4. Whisper 모델 다운로드 진행률 UI (Phase 2 bootstrap) 5. 설정 페이지에 모델 선택지(gemma4:e2b / gemma3:4b fallback) 6. 아이콘 디자인 준비 후 `build/icon.png|.ico` 추가 + win.icon 복원 ### 참고 파일 - `.gitlab-ci.yml` — 현재 Windows runner 단독 파이프라인 - `C:\GitLab-Runner\config.toml` — environment PATH 주입, 백업은 `config.toml.bak` - `apps/desktop/resources/sox/` — 바이너리 직접 커밋(약 6MB) ## 배포 파이프라인 확장 (2026-04-15 PM) ✅ ### 추가 변경 - **installer.nsh**: `NSISdl`로 `aka.ms/vs/17/release/vc_redist.x64.exe` 사일런트 다운로드/설치. 실패 시 수동 설치 안내. - **LocalLLMService.pullModel(modelId)**: Ollama `/api/pull` 스트리밍 구독 → `pull-progress` 이벤트 emit. - **LLM IPC 확장**: `LLM.PULL_MODEL` 핸들러 + `LLM.PULL_PROGRESS` 이벤트 브로드캐스트. preload에 `llm.pullModel()`, `llm.onPullProgress()` 추가. - **OnboardingModal 재작성**: `gemma4:e4b` 미설치 자동 감지(15초 간격 polling) → 진행률 모달(0~100%) → 성공 시 `config.onboardingCompleted=true` 저장. - **i18n ko/en**: `onboarding.*` 키 14개 추가. ### 태그 푸시 - `v0.1.0-alpha` 푸시 완료 → GitLab `d3r0/voice` 파이프라인 트리거됨. - CI 파이프라인: lint/typecheck/test (Linux docker) → package-windows (Windows runner, 태그 필요) → release-create (GitLab Releases 자동 생성). ### 다음 작업 1. **Windows GitLab Runner 등록** (이 PC, yunchan8804) - gitlab-runner.exe 설치 + shell executor - tags: `windows`, `shell` - Python 3.11 권장(현재 3.13 — faster-whisper/ctranslate2 호환 확인 필요) 2. 첫 파이프라인 실행 관찰 + 실패 시 튜닝 3. Whisper large-v3 다운로드 진행률 UI (Bootstrap 2단계) 4. 설정 페이지에서 모델 변경(gemma4:e2b/gemma3:4b) 선택지 노출 ## 배포 파이프라인 기반 구축 (2026-04-15) ✅ ### 결정 사항 - **기본 LLM 모델**: `gemma4:e4b` (9.6GB, 텍스트+이미지+오디오 멀티모달) - **Ollama 배포 방식**: 바이너리 **번들** (포터블 zip을 `resources/ollama/`에 동봉) - **Whisper 기본 모델**: `large-v3` (3GB, 첫 실행 시 자동 다운로드) - **Sidecar 슬림화**: `torch`, `pyannote.audio` 제거 → GPU 감지는 `ctranslate2` 사용. `/diarize` 엔드포인트 삭제. 화자 구분은 LLM 추정 경로로 폴백(기존 MeetingModeService가 자동 처리). - **SaaS 지향**: 고정밀 화자 구분은 추후 서버 사이드 API로 제공 예정. ### 변경된 파일 - `apps/desktop/sidecar/main.py`: torch/pyannote 의존 제거, `/diarize` 삭제, ctranslate2 GPU 감지 - `apps/desktop/sidecar/requirements.txt`: torch/pyannote 제거 (faster-whisper + fastapi + uvicorn + numpy만) - `apps/desktop/src/main/utils/paths.ts`: `getBundledOllamaPath()` 추가 - `apps/desktop/src/main/services/LocalLLMService.ts`: `_findOllamaBinary()`가 번들 ollama를 1순위로 탐색 - `apps/desktop/electron-builder.yml`: `resources/ollama/` extraResources + `nsis.include: build/installer.nsh` - `apps/desktop/scripts/download-ollama.ps1` (Windows), `download-ollama.sh` (mac/linux) 신규 - `apps/desktop/build/installer.nsh` 신규 — VC++ 재배포 확인 + 언인스톨 시 번들 ollama 종료 - `.gitlab-ci.yml`: Windows self-hosted runner에서 `sidecar 빌드 → sox 다운로드 → ollama 다운로드 → electron-builder` 파이프라인. 태그 푸시 시 GitLab Release 자동 생성. - `.gitignore`: `resources/ollama/` 빌드 산출물 무시 ### 다음 작업 1. Windows self-hosted GitLab runner 등록 (tags: `windows`, `shell`) 2. 첫 실행 bootstrap UI — `gemma4:e4b` + Whisper `large-v3` 다운로드 진행률 모달 - 위치: `Dashboard` 진입 전 게이트 - IPC 채널: 설계서 02의 `ipc-channels.ts`에 `setup:pull-model`, `setup:download-whisper`, `setup:progress` 추가 필요 3. VC++ 재배포 자동 다운로드(inetc plugin 검증 필요) 또는 번들화(25MB 추가) 4. 첫 태그 푸시(예: `v0.1.0-alpha`)로 CI 검증 ### 차단 이슈 (기존) - `@nut-tree-fork/nut-js` 포크 사용 (변경 없음) --- ## RN CLI 전환 (2026-04-13) ✅ ### Expo → React Native CLI 전환 - apps/mobile-rn: RN 0.85 + React 19 + TypeScript - 기존 Expo 코드(apps/mobile-expo) → RN CLI용 6개 화면 이식 완료 - Navigation: @react-navigation/native + bottom-tabs + native-stack - 공유 패키지: @d3ro/ui-native, @d3ro/i18n, @d3ro/core, @d3ro/api-client 연결 ### Android 빌드 환경 (Windows) - JDK 17 (Microsoft OpenJDK 17.0.18) - Android SDK: platform-34, build-tools-34, NDK 27.1, CMake 3.22.1 - **Windows NDK CMake 버그 워크아라운드**: libc++_shared.so가 자동 링크 안 됨 - `scripts/fix-windows-ndk.sh`: ninja 파일 후패치 스크립트 - 빌드 순서: `gradlew configureCMakeDebug` → `fix-windows-ndk.sh` → ninja 수동 빌드 → `gradlew assembleDebug -x :react-native-screens:buildCMakeDebug[arm64-v8a]` - `reactNativeArchitectures=arm64-v8a` (32-bit 제외) - `newArchEnabled=false` ### APK 설치 성공 - SM_F956N (갤럭시 폴드) 무선 디버깅으로 설치 확인 - Metro 번들러 연결 확인 (RN v0.85, Metro v0.84.2) ### 다음 작업 (이어서) - Metro 번들 에러 확인/수정 (화면 렌더링 테스트) - expo-av → react-native-audio-recorder-player 교체 (RecordScreen) - postinstall 스크립트로 Windows NDK 패치 자동화 - 커밋 후 apps/mobile-expo 정리 여부 결정 --- > 규칙 13: 작업 완료 즉시 이 파일 갱신 의무 ## Phase M-2: D3RO 디자인 시스템 모바일 적용 (2026-04-13) ✅ ### DS 컴포넌트 (packages/ui-native) - 기존 4개 + 신규 5개 = **총 9개**: MetalCard, PhosphorText, Led, PhysicalButton, ScreenPanel, WaveBars, AppStatusBar, Header, FilterChip - 테마 토큰 SSOT: d3roNativePalette / d3roNativeTypo / d3roNativeRadius / d3roNativeFonts - WaveBars: core Animated API 사용 (reanimated 의존성 제거) ### 탭 화면 D3RO 디자인 적용 (5개 + 로그인) - dash.tsx: Session Overview (ScreenPanel), 2x2 Stats Grid, Backend/Tier Info, Usage Panel - history.tsx: FilterChip (ALL/SAVED/PROC), 날짜 그룹핑, LED 상태, 단어수 배지 - record.tsx: WaveBars 애니메이션, 녹음 상태머신, 실시간 전사 UI - talk.tsx: 채팅 버블 (AI/User), 날짜 배지, 입력 영역 - settings.tsx: 섹션별 카드 (Account, Backend, Preferences) - login.tsx: LED 로고, OAuth 3종, 이메일/비번, D3RO 스타일 ### i18n 연결 - I18nProvider를 root layout에 연결 - 모바일 전용 키 80+개 추가 (ko.json, en.json) - 모든 UI 문자열 t() 함수 사용 ### 인프라 - apps/mobile을 모노레포 workspaces에 추가 - saasMode 분기 제거 커밋 완료 ### 알려진 이슈 - @types/react 버전 충돌 (desktop=19, mobile=18) → TS2786만 발생, Metro에 무영향 - react-native-screens bob build 실패 → --ignore-scripts로 해결, EAS Build에서는 정상 --- ## Admin 콘솔 리디자인 + 랜딩 SaaS 전환 (2026-04-13) ✅ ### Admin CRM 콘솔 스타일 리디자인 - 전체 페이지 D3RO Console 스타일 적용 (Overview, Users, Subscriptions, Audit Log + 모든 상세) - `console-theme.ts` 디자인 토큰 시스템 (C 색상, FONT, panelSx, tableSx, filterBtnSx, statusBadgeSx) - `globals.css` — 스캔라인 애니메이션, 그리드 패턴, 글로우 효과, 커스텀 스크롤바 - JetBrains Mono 폰트, 검정 배경, 라운드 패널, 오렌지 액센트 - MetalCard/PhosphorText → Box + sx 전환 (MUI는 유지, d3ro DS 컴포넌트 미사용) ### SSE 스트리밍 Premium LLM 연결 - VoiceConversationService에 Premium/Local LLM 라우팅 추가 - `_createChatStream()`: configGet('llmBackend') 기반 분기, Premium 실패 시 Local fallback - PremiumLLMService.chatStream() → Anthropic SSE → 토큰 단위 yield → TTS 파이프라인 ### 코드 정리 - **Supabase 클라이언트 공통화**: packages/api-client에 supabase-browser.ts, supabase-server.ts 추출 - Browser: 싱글톤 캐싱 + isSupabaseConfigured() 완전 공유 - Server: CookieStore 인터페이스 팩토리 패턴 (next/headers 의존성 분리) - apps/web, apps/admin → 3줄 re-export 래퍼로 축소 - **RPC 함수 타입 9개 추가**: packages/api-client/src/types.ts Functions 섹션 - generate_invite_token, user_team_ids, user_admin_team_ids, match_knowledge_chunks - consume_quota, increment_daily_usage, admin_usage_by_feature, admin_top_users, admin_dau ### 랜딩 페이지 SaaS 구독 모델 전환 - Pricing: 1회 결제 → 월간($9.9/$19.9) / 연간($99/$199) 토글 - 10개 언어 i18n 전체 동기화 (pricing, FAQ, CTA) - FAQ: 구독 관련 답변 업데이트 - CTA: "구독 없음" → "프리미엄 기능, 당신의 조건으로" - 다운로드 URL: placeholder (#) — 추후 실제 URL 제공 시 교체 --- ## V2-6.1: 3단계 권한 체계 (2026-04-12) ✅ ### 변경 내역 - **DB**: `profiles_role_check`에 `manager` 추가, RLS 정책 7개 재작성 (manager 읽기, admin 쓰기, manager 구독 UPDATE) - **Edge Functions 4개**: `requireManager`/`requireAdmin` 3단계 분기 적용 + 배포 완료 - **Frontend**: `admin-guard.ts` 3단계 가드, UI 컴포넌트 권한별 표시 분기 - **토큰 갱신 버그 수정**: `callAdminApi`에서 `getSession()` → `getUser()` 선행 호출 ### 권한 체계 | 기능 | manager | admin | super_admin | |------|---------|-------|-------------| | 조회 전체 | ✅ | ✅ | ✅ | | 구독 수정 + 메모 | ✅ | ✅ | ✅ | | 구독 생성 (VIP) | ❌ | ✅ | ✅ | | 구독 삭제 | ❌ | ✅ | ✅ | | role 변경 (user↔manager) | ❌ | ✅ | ✅ | | role 변경 (→admin/super_admin) | ❌ | ❌ | ✅ | --- ## Phase V2-6: Admin CRM 고도화 (2026-04-13 09:30) ✅ ### DB 마이그레이션 (`20260413000004_admin_enhancement.sql`) - `audit_log` 테이블: admin_id, action, target, before/after JSON diff, memo - `subscriptions.admin_note` 컬럼 추가 - `profiles.role` CHECK 확장: user/admin/super_admin - RLS: admin+super_admin 조회, super_admin 쓰기 - SQL RPC 3종: admin_usage_by_feature, admin_top_users, admin_dau ### Edge Functions 4개 (admin-users, admin-subscriptions, admin-payments, admin-audit-log) - admin-users: 유저 목록/상세 + super_admin role 변경 - admin-subscriptions: 구독 CRUD (생성/수정/소프트삭제) + 메모 필수 + 감사로그 자동 기록 - admin-payments: DB + Payple API 병행 결제 이력 조회 - admin-audit-log: 감사로그 목록/상세 (필터: target_type, admin_id, 날짜) - 공유 유틸: `_shared/admin-auth.ts` (권한 검증), `_shared/audit.ts` (감사 기록) ### Swagger UI (`apps/admin-swagger/`) - 독립 정적 페이지 + OpenAPI 3.0 spec - Bearer token 입력으로 admin 인증 ### Admin CRUD 웹페이지 (apps/admin) - 구독 상세/수정/삭제: `/subscriptions/[id]` + SubscriptionForm + MemoDialog - 구독 생성 (VIP 부여): `/subscriptions/new` - 유저 상세: role 변경 버튼 (super_admin만) + RoleChangeDialog - 감사로그: `/audit-log` 목록 + `/audit-log/[id]` 상세 diff (AuditDiffViewer) - 결제 이력: PaymentHistory 컴포넌트 (DB + Payple 병행) - 사이드바: Audit Log 메뉴 + API Docs 외부 링크 ### recharts 차트 3종 - FeatureUsageChart: feature별 일간 StackedBar - DauChart: DAU 추이 Line - TopUsersChart: 유저별 사용량 Top 20 HorizontalBar - Usage 페이지: 차트 + 기간 필터(7/14/30일) + 테이블 ### 권한 체계 - super_admin: 구독 CRUD, role 변경, 구독 삭제 (위험 작업) - admin: 전체 조회 전용 - admin-guard.ts: requireAdmin(), requireSuperAdmin(), isSuperAdmin() --- ## Admin CRM 독립 프로젝트 (2026-04-12 22:00) ✅ - `apps/admin` Next.js 독립 앱 (포트 3001) — apps/web에서 완전 분리 - OAuth 로그인 + app_metadata 기반 admin 가드 - RLS: 재귀 참조 수정 → `auth.jwt() -> app_metadata` 기반 - middleware: Supabase 세션 갱신 - 4개 페이지: Overview / Users / Subscriptions / Usage ## 마스터플랜 4개 작업 (2026-04-12 21:00) ✅ ### 작업 1: LemonSqueezy 코드 정리 (36031ac) - LicenseService.ts ~150줄 삭제 (API 상수/인터페이스/메서드 4개) - activate/deactivate 로컬 키 검증 전용으로 단순화 - site/ taxNote: LemonSqueezy → Payple (10 locale) ### 작업 2: Payple 정기 갱신 크론 (d3c2a43) - `payple-renew` Edge Function: 만료 구독 자동 재결제 + 3회 실패 시 다운그레이드 - migration: `subscriptions.renewal_failures` 컬럼 추가 - GitHub Actions: 매일 01:00 UTC 스케줄 - payple-webhook: 결제 완료 시 renewal_failures=0 리셋 ### 작업 3: 관리자 CRM 웹페이지 (667c092) - migration: `profiles.role` 컬럼 + admin RLS 정책 3개 - `/admin` 대시보드: 총 유저, 유료 구독자, API 호출, 만료 예정 - `/admin/users`: 검색/필터/페이지네이션 + 유저 상세 - `/admin/subscriptions`: 상태 필터 - `/admin/usage`: feature별 집계 (7/14/30일) - Sidebar에 Admin 네비게이션 추가 ### 작업 4: SSE 스트리밍 Premium (7467bd6) - `sse-parser.ts`: Anthropic SSE 파서 (content_block_delta.text 추출) - `CloudSyncService.invokeFunctionStream()`: raw fetch + ReadableStream - `PremiumLLMService.chatStream()`: stream=true, 토큰 단위 yield + fallback --- ## 라이선스 UI 빅뱅 리라이트 (2026-04-12 20:00) ✅ LemonSqueezy 키 입력 방식 → Supabase 구독 + Payple 결제 기반 UI로 전면 교체 ### 변경 내역 - `LicenseTab.tsx` / `LicenseModal.tsx`: 키 입력 제거, 클라우드 인증 + 업그레이드 버튼 - `useLicenseState.ts` 훅: 공용 라이선스+클라우드 상태 관리 (중복 제거) - `PREMIUM_MODEL_LIMITS`: 3곳 중복 → `@d3ro/core/constants` 단일 소스 - i18n: LemonSqueezy 전용 키 18개 삭제, 구독 관련 키 13개 추가 (12 locale) - `DashboardPage`: `dashboard.model*` → `license.model*` i18n 키 통일 ### 알려진 이슈 - LicenseService.ts의 activate/deactivate 메서드 + IPC 핸들러: 코드 잔존 (UI 미사용) - 다음 사이클에서 LemonSqueezy API 코드 완전 제거 예정 --- ## Phase 3.2-B 배포/확인/버그수정 (2026-04-12 19:30) ✅ ### 배포 완료 - DB migration `20260412000002_payple_billing.sql` → Supabase 리모트 적용 - Payple secrets (테스트 키) → Supabase Secrets 설정 - Edge Functions 3개 (`payple-checkout`, `payple-webhook`, `payple-manage`) 배포 ACTIVE - Electron `OPEN_BILLING`: 개발 환경 `localhost:3000` / 프로덕션 `d3ro.dev` 분기 추가 ### 대시보드 시각 확인 완료 - Premium LLM LED (CLAUDE) 초록색 표시 ✓ - 티어 카드 (FREE) 앰버 LED ✓ - 사용량 바 + Premium 쿼터 (Haiku 268/주간) ✓ - i18n 키 14개 전부 존재 확인 ✓ ### 세션 만료 버그 수정 - `CloudSyncService.ts`: stored token 없을 때 `auth-changed { user: null }` emit 추가 - 렌더러 CloudSyncSection이 초기 상태를 올바르게 수신하게 됨 --- ## Phase 3.2-B Payple 결제 연동 + 대시보드 Premium 상태 (코드 구현, 2026-04-12) ✅ ### A. Payple 결제 연동 Stripe 기반 결제를 Payple(한국 PG)로 전면 교체. 웹(apps/web) 결제 페이지가 핵심. **신규 파일:** - `server/supabase/functions/_shared/payple.ts` — Payple API 래퍼 (auth, billing, cancel, deleteBillingKey) - `server/supabase/functions/payple-checkout/index.ts` — 빌링키로 첫 결제 + 구독 활성화 - `server/supabase/functions/payple-webhook/index.ts` — 결제완료/취소 이벤트 처리 - `server/supabase/functions/payple-manage/index.ts` — 구독 취소 (빌링키 해지) - `server/supabase/migrations/20260412000002_payple_billing.sql` — payment_provider, payple_payer_id, payple_pay_oid 컬럼 - `apps/web/src/components/billing/payple-checkout-button.tsx` — Payple JS SDK 결제창 호출 - `apps/web/src/components/billing/payple-manage-button.tsx` — 구독 관리/취소 다이얼로그 **수정 파일:** - `apps/web/src/app/(app)/billing/page.tsx` — 티어 통일(team→pro_plus) + Payple 컴포넌트로 교체 - `apps/desktop/src/renderer/components/LicenseModal.tsx` — 업그레이드 → shell.openExternal(웹 결제) - `packages/core/src/ipc-channels.ts` — LICENSE.OPEN_BILLING 추가 - `apps/desktop/src/preload/index.ts` — license.openBilling() API - `apps/desktop/src/main/ipc/license-handlers.ts` — OPEN_BILLING 핸들러 - `server/supabase/config.toml` — payple-* verify_jwt=false ### B. 대시보드 Premium 상태 표시 - DashboardPage services 배열에 PREMIUM LLM LED 추가 → CrtDisplay 자동 렌더 - 백엔드 인디케이터 카드(Local/Premium) + 티어 카드 - 사용량 바: 모든 티어에서 표시 + Premium 모델별(Haiku/Sonnet/Opus) 쿼터 섹션 ### i18n 12개 locale에 14개 키 추가 (service.premiumLlm, dashboard.currentBackend 등) ### 알려진 이슈 (이월) 1. ~~**세션 만료 렌더러 미동기화**~~ — ✅ 수정 완료 (stored token 없을 때 auth-changed null emit) 2. **LemonSqueezy 코드 잔존** — LicenseService.ts에 @deprecated 코드. 다음 사이클에서 정리 3. **Payple 테스트 키** — 라이브 전환 시 PAYPLE_CST_ID/CUST_KEY/REFUND_KEY/CLIENT_KEY Supabase Secrets 등록 필요 4. **Payple 정기 갱신 크론** — payple-renew 미구현. pg_cron 또는 외부 스케줄러 필요 ### 백로그 - **관리자 백엔드 서비스 웹페이지**: 사용자 관리 CRM, 구독 현황 대시보드, 쿼터 관리, 결제 이력 조회. 별도 admin/ 앱 또는 apps/web 내 admin 라우트 그룹으로 구현 예정. - **Payple 정기 갱신 크론** (payple-renew) - **SSE 스트리밍 Premium** --- ## Phase 3.2 Premium LLM 완결 (2026-04-12) — 빅뱅 8/8 마지막 성공 기준 ✅ Supabase Edge Function(llm-proxy)을 통해 Anthropic Claude를 호출하는 PremiumLLMService 구현. 사용자가 Settings에서 Local/Premium 백엔드를 선택하면 VoiceModeService가 자동 분기하고, Premium 실패 시 Local로 silent fallback + 상단 중앙 배너 알림. ### 커밋 3개 (이번 세션) | SHA | 내용 | |---|---| | `a744551` | fix: Bug 13 전면 해소 — Conversation 에러 배너 + Dictation 빈 STT 가드 + recording tip 타이머 leak | | `d397bcb` | feat: LLM 기본 모델 qwen3:4b → gemma4:e4b 전면 전환 + think:false 안전장치 | | `6e52c18` | feat: Phase 3.2 Premium LLM — Anthropic Claude 파이프라인 + 모델별 쿼터 + SaaS UI | ### 실측 결과 | 경로 | LLM refine 시간 | |---|---| | 이전 qwen3:4b (local, reasoning) | 42.9초 | | gemma4:e4b (local, non-reasoning) | 3.9초 | | **Claude Haiku (premium)** | **1.6~3.2초** | ### 주요 신규/변경 파일 (23개) **신규**: `PremiumLLMService.ts`, `llm-prompts.ts`, migration `20260412000001` **서버**: `quota.ts` (모델별 쿼터), `llm-proxy/index.ts`, `config.toml` (verify_jwt=false) **데스크톱**: `VoiceModeService` (router+fallback), `CloudSyncService` (invokeFunction, getAccessToken async), `ConfigService` (llmBackend), `LicenseService` (쿼터 250/1500), `llm-handlers` (safeSendToRenderer+premium IPC), `preload` (premium API) **UI**: `SettingsModal` (backend 드롭다운+conditional), `LicenseModal` (SaaS 전환), `AppLayout` (fallback 배너) **타입**: `AppConfig.llmBackend`, `SubscriptionTier pro_plus`, `IPC_CHANNELS.LLM.PREMIUM_*` ### 모델별 쿼터 정책 | | Free | Pro ₩9,900 | Pro+ ₩29,900 | |---|---|---|---| | 로컬 | ∞ | ∞ | ∞ | | Haiku | 250/주간 | 1,500/일 | ∞ | | Sonnet | ❌ | 300/일 | 1,500/일 | | Opus | ❌ | 50/일 | 300/일 | ### 알려진 이슈 (이월) 1. **세션 만료 시 렌더러 미동기화** — CloudSyncService.init()에서 stored token 없을 때 auth-changed null 미emit → UI 불일치 (memory/feedback_session_expiry_bug.md) 2. **Payple 결제 연동 미완** — 업그레이드 버튼 클릭 시 "결제 연동 준비 중 (Payple)" alert 스텁. Phase 3.2-B에서 실제 연결 3. **대시보드 Premium 상태 표시** — 현재 대시보드에 Premium LLM 쿼터/모델 정보 미노출 4. **Supabase 2026 verify_jwt 비호환** — sb_publishable_ 키와 Gateway JWT 검증 비호환으로 verify_jwt=false 설정. 함수 내부 requireUser()에서 직접 인증 처리 중 ## 빅뱅 Phase 5 Part 7 (2026-04-11) — Bug 13 전면 해소 + Gemma4 기본 모델 전환 ✅ Part 6에서 Voice Conversation 몰입 패널까지 가고 남은 숙제였던 **Bug 13 전면 해소**와 사용자 실측에서 발견된 **LLM refine 42.9초 병목**(qwen3 reasoning 모델 strip empty fallback)을 한 사이클에 정리. 받아쓰기 한 바퀴 51초 → 기대 7~10초로 대폭 단축 예상. ### 7-A. Voice Conversation 에러 배너 UI (Part 6 후속) `VoiceConversationPage.tsx`의 `onError` 콜백이 기존에는 주석만 있고 UI 피드백이 없었음. MUI `Snackbar + Alert(severity="warning" variant="filled")` 하단 중앙 4초 auto-hide 배너 추가. 에러 매핑 헬퍼 `formatErrorMessage`: - `phase === 'stt' && message.startsWith('no speech')` → i18n `conversation.error.noSpeech` - 그 외 → `${phaseLabel}: ${rawMessage}` (phaseLabel 3종 i18n) **i18n 키 4개 추가** (ko/en): `conversation.error.phase.{stt,llm,tts}` + `conversation.error.noSpeech` ### 7-B. Dictation Bug 13 가드 (VoiceModeService) `_transcribe()`에 2중 가드 추가 — VoiceConversationService와 동일 패턴. 1. **minBytes 가드** (16kHz 16bit mono 기준 0.5초 = 16000 bytes 미만) → `_handleError(STTAudioTooShort, 'No speech detected...')` → recording-tip popup이 'error' 상태로 전환 → 3초 자동 숨김 2. **빈 result.text 가드** → `_handleError(STTNoAudioData, 'No speech detected...')` (동일 경로) 실측 로그에서 확인된 기존 버그: `1.5초 무음 오디오 → VAD 전체 filter → 빈 전사 → LLM 5초 대기 → 빈 session completed → 빈 history entry 생성` 시퀀스가 모두 차단됨. ### 7-C. Recording Tip hide 타이머 leak 수정 (VoiceModeService) 사용자 실측에서 발견된 추가 버그 — "받아쓰기 꾹 누르고 있는데 2초 뒤에 popup이 사라짐". 원인은 `_handleError`의 `setTimeout(() => hideRecordingTip(), 3000)`이 핸들로 보관되지 않아 다음 세션 시작 후에도 예약대로 발사되어 현재 진행 중인 recording tip을 hide 시킴. Bug 13 가드 추가로 에러 경로가 자주 타면서 드러난 기존 잠재 버그. **수정**: `_errorHideTimer: NodeJS.Timeout | null` 필드 추가 → `_handleError`에서 핸들 보관 + 기존 타이머 clearTimeout 후 재예약, `_startSession` 초기화 블록에서 `_errorEmitted = false`와 함께 clearTimeout, `dispose()`에서도 leak 방지용 cleanup. 실측 확인: 4.5초 hold 성공, 전사 `"Hey, yeah, yeah..."` 정상 완료. ### 7-D. LLM 기본 모델 전면 교체 — qwen3:4b → gemma4:e4b ⭐ **발견한 병목**: 받아쓰기 한 사이클 51.4초 = STT 7.4초(PyTorch 미설치, CPU int8) + **LLM 43초(!)** + insert 1초. LLM 43초의 원인은 `qwen3:4b`가 reasoning 모델이라 `...` 블록을 길게 생성 → `stripReasoningBlocks` 후 **빈 문자열 → 원본 transcript fallback**. 즉 43초를 기다렸는데 결과는 원본 그대로. **해결**: 2026-04-02 릴리스된 Google **Gemma 4 e4b** (4.5B effective params)를 기본으로 교체. non-reasoning 기본 동작 + Ollama v0.20+ `think: false` 파라미터로 2중 방어. **변경된 위치** (한 번에): - `LocalLLMService.ts` — 3개 default fallback (`generate`/`streamGenerate`/`chatStream`) + Ollama 요청 body에 `think: false` 명시 추가 + NO_THINK 주석 업데이트(legacy 표기) - `OnboardingModal.tsx:246` + `OllamaGuideModal.tsx:133` — pull 명령어 변경 - `packages/i18n/src/locales/{12개}.json` — `settings.ollamaHint`, `ollama.step2.alt` 2개 키 (qwen3:4b → gemma4:e4b, qwen3:8b → gemma4:26b) - `site/src/i18n/locales/{10개}.ts` — 마케팅 페이지 detail 문자열 `qwen3 / llama3 / gemma3` → `gemma4 / llama3.2 / phi4` - `site/src/sections/HowItWorks.tsx:47` — 파이프라인 시각화 `qwen3:4b @ localhost` → `gemma4:e4b @ localhost` - `apps/desktop/tests/main/services/VoiceModeService.test.ts:58` — test fixture `llmModelId` - `docs/design/00-master-architecture.md` — `LLMConfig.model` 주석 + `CONFIG_DEFAULTS.llm.model` - `docs/design/05-external-engine-integration.md` — 6개 API 스키마 예시 + 2개 OllamaClient 코드 예시 + LLM 모델 추천 표(gemma4:e4b를 최상위로 재정렬, qwen3를 reasoning 경고와 함께 후순위로) + 권장 JSON 설정(think:false 추가) - `docs/phases/phase-14-meeting-mode.md` — 컨텍스트 윈도우 표 갱신 **리서치 경로**: Ollama 라이브러리 확인 → gemma4는 E2B(2.3B), E4B(4.5B), 26B MoE, 31B Dense 4종 + 31b-cloud. Thinking은 `<|think|>` 토큰을 system prompt에 넣어야 켜지는 opt-in 방식. 기본 추천 E4B. 사용자는 이미 Ollama 0.20.5 사용 중(gemma4 요구 0.20+). ### 변경 파일 (대규모) — 40+ 파일 | 영역 | 파일 수 | 성격 | |---|---|---| | 코드 (메인) | 1 | LocalLLMService.ts | | UI 컴포넌트 | 3 | OnboardingModal, OllamaGuideModal, VoiceConversationPage | | 서비스 (버그 수정) | 1 | VoiceModeService | | 테스트 | 1 | VoiceModeService.test | | i18n (desktop) | 12 | ko/en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th | | i18n (site) | 10 | ko/en/ja/zh/es/fr/de/pt/ru/vi | | 마케팅 컴포넌트 | 1 | HowItWorks.tsx | | 설계 문서 | 3 | 00-master, 05-external-engine, phase-14 | ### 검증 게이트 | 게이트 | 결과 | |---|---| | desktop `tsc --noEmit` | ✅ EXIT 0 | | Bug 13 Dictation minBytes 가드 | ✅ 로그 확인 (전에 빈 history entry 생성되던 경로 차단) | | Bug 13 Conversation 배너 UI | ✅ Snackbar severity=warning filled 노출 | | Recording tip hide 타이머 leak | ✅ 4.5초 hold 성공, 전사 정상 | | 기본 모델 gemma4:e4b 전환 | ⏳ **사용자 수동 pull + 실측 대기** | ### 다음 세션 액션 1. 사용자 `ollama pull gemma4:e4b` (~4.5GB) 2. 받아쓰기 한 사이클 실측 → refine 지연 측정 → **43초 → 몇 초 기대** 3. 결과 컨펌되면 커밋 정리 + 빅뱅 8/8 마지막 1개(`Phase 3.2 PREMIUM_LLM`)로 전환 4. Phase 3.2 선행 조건: 사용자가 `supabase secrets set ANTHROPIC_API_KEY=... OPENAI_API_KEY=...` ## 빅뱅 Phase 5 Part 6++ (2026-04-11) — U1/U3/U4 남은 이슈 정리 ✅ ### U1 — Realtime TIMED_OUT 자동 재구독 (CloudSyncService.ts) Publication/RLS/setAuth 모두 설정 완료 상태에서도 매 기동 시 `TIMED_OUT`이 찍히는 증상. 근본 원인은 Supabase 서버 측 transient(네트워크/WebSocket 타임아웃) 의심으로 판정. Auto push(Phase 3.3) fallback이 완벽히 동작해 블로커는 아니지만, 다른 기기에서의 원격 변경 pull을 놓치는 window가 생김. **변경**: `subscribe()` 콜백에 지수 백오프 자동 재구독 추가. - `SUBSCRIBED` → retry count reset + 타이머 클리어 - `TIMED_OUT` / `CHANNEL_ERROR` / `CLOSED` → `_scheduleRealtimeRetry()` - 백오프: 1s → 3s → 10s, 3회 초과 시 포기하고 경고 로그(Auto push로만 동작) - `stopRealtime()`에서 타이머 cleanup + count reset ### U3 — Refresh token 실패 시 재인증 이벤트 emit (CloudSyncService.ts) `init()`의 `refreshSession` 실패 브랜치에서 기존에는 `warn` 로깅 + 토큰 삭제만 수행 → 사용자는 "왜 로그아웃됐지?" 상태로 방치. 재현이 쉽지 않지만 장시간 idle 후 발생 가능성 있어 예방적 개선. **변경**: - `logger.warn` → `logger.error`(에러 레벨 승격) - `sync-error` 이벤트 emit (`"Sign-in session expired. Please sign in again."`) - `auth-changed { user: null }` emit (renderer 게이트 재잠금) - `try/catch`의 catch 브랜치도 동일 처리 (기존에는 로깅만) ### U4 — package-lock optional dep (확인 후 유지 결정) `package-lock.json`에 rollup의 전 플랫폼 `optionalDependencies`(darwin/win32/linux/freebsd/android)가 기록돼 있음. Mac `npm install --force` 시 win32 관련 경고가 나왔지만 런타임 영향 없음. `apps/desktop/package.json:56`에 `@rollup/rollup-win32-x64-msvc`가 명시적 의존으로 선언되어 있는 이유는 Windows 빌드자가 사전에 binding을 확보하기 위함. **결정**: **유지**. 크로스플랫폼 개발/CI 효율 + Win 빌드자 경험을 우선. 향후 lock 재생성 시에도 선택적 제거 불필요. 이 이슈는 **close**. ### 변경 파일 (2) - `apps/desktop/src/main/services/CloudSyncService.ts` — U1 재구독 + U3 재인증 이벤트 - `memory/project_status.md` — 본 섹션 ## 빅뱅 Phase 5 Part 6+ (2026-04-11) — U7 AudioCaptureService Mac 분기 ✅ `AudioCaptureService.getDevices()`가 `powershell -NoProfile`만 호출하던 것을 `process.platform` 분기로 교체. Mac에서 `system_profiler SPAudioDataType -json`으로 CoreAudio input 디바이스 열거(`coreaudio_device_input > 0` 필터). macOS SoX는 실제 캡처 경로에서 `-d`(default) 고정이므로 `deviceId='default'`, `label`만 사용자 구분용으로 노출. 기존 "Failed to enumerate audio devices via PowerShell" 경고 + 1 device fallback 해소. **변경 파일 (1)**: `apps/desktop/src/main/services/AudioCaptureService.ts` — `_getDevicesWindows` / `_getDevicesMac` helper 추출 + 분기 디스패처. **검증**: Electron 재기동 후 로그 `Found 2 audio device(s) on darwin` 확인 (경고 없음). ## 빅뱅 Phase 5 Part 6 (2026-04-11) — Voice Conversation UX 몰입 패널 ✅ Part 5-C 설계를 구현으로 완결. 사용자가 listening 상태에서 풀 몰입 계측기 모드 + 사운드 피드백 + 상태별 UI 분기를 실제로 경험할 수 있는 상태. **변경 파일 (6)** - `packages/core/src/ipc-channels.ts` — `VOICE_CONVERSATION.AUDIO_LEVEL` 채널 추가 - `apps/desktop/src/main/services/SoundEffectService.ts` — `SoundName`에 `'chime'` 추가 (recording-stop.wav 재사용) - `apps/desktop/src/main/services/VoiceConversationService.ts` — `_onAudioLevel` forwarding + 사운드 훅 4개(start/stop/chime/error) + Bug 13(빈 STT 피드백) - `apps/desktop/src/preload/index.ts` — `voiceConversation.onAudioLevel` 구독 API - `apps/desktop/src/renderer/components/voice-conversation/VoiceRecordingPanel.tsx` — **신규** 9바 phosphor waveform(BAR_COUNT=9, cos 분포, 100ms, SMOOTHING=0.5, RANDOM_FACTOR=0.35) + REC LED + elapsed 타이머 + "SPEAK NOW" 힌트 - `apps/desktop/src/renderer/pages/VoiceConversationPage.tsx` — state 분기(listening=VoiceRecordingPanel / 그 외=메시지 리스트) + thinking 시 typing indicator 버블 + TextField listening 시 숨김·thinking/speaking 시 disabled - `packages/i18n/src/locales/{ko,en}.json` — `conversation.recording.hint`, `conversation.thinking.placeholder`, `conversation.state.recording` 3개 키 **해소 이슈** - U6 Voice Conversation UX 몰입 패널 구현 - U8 Bug 13 빈 STT 피드백 부재 (minBytes 미달 + VAD 무음 판정 양쪽 모두 `_emitError('stt', ...)` 훅 추가) **검증** - desktop `tsc --noEmit` EXIT 0 - Vite renderer HMR 자동 반영, Electron kill + nohup 재기동(PID 9635/9641) - 사용자 녹음 테스트: 한국어 5.1초 오디오 전사 성공(`"에이에이에이에이 또 검중이래요"`) → 파이프라인 살아있음 확인 ## Mac 환경 부트스트랩 (2026-04-11) Windows → Mac 핸드오프 완료 (`memory/handoff-latest.md` 참조). | 항목 | 상태 | |---|---| | Homebrew | ✅ 기존 설치 | | Python 3.13 | ✅ 기존 설치 | | Node 22.22.2 (brew) | ✅ 신규 설치 | | sox 14.4.2 (brew) | ✅ 신규 설치 | | Ollama | ❌ 미설치 (로컬 LLM 필요 시 `brew install ollama && ollama pull qwen3:4b`) | | `npm install --force` | ✅ 1142 패키지 (rollup win32 옵셔널 dep 무시) | | `@electron/rebuild@3 --version=33.4.11` | ✅ native 모듈 재빌드 완료 | | desktop `tsc --noEmit` | ✅ | | web `tsc --noEmit` | ✅ | | web `next build` | ✅ (15 라우트, 5.8s compile) | | api-client `vitest` | ✅ (19 passed, 283ms) | | git working tree | ✅ clean (`package-lock.json` 포함 변경 없음) | **남은 사용자 액션** - OAuth Provider (Google/GitHub) 연결 — Supabase 대시보드 → Auth → Providers - 외부 API 키 — ANTHROPIC / OPENAI / GOOGLE_CLOUD_STT / RESEND / STRIPE (아래 Edge Function Secrets 섹션) - (선택) `brew install ollama` + `ollama pull qwen3:4b` - (필요 시) `npm run dev`로 Electron 앱 수동 검증 — 첫 실행 시 Accessibility/마이크/Apple Events 권한 다이얼로그 승인 ## Supabase 프로젝트 연결 (2026-04-11) **프로젝트**: `yunchan8804-blip's Project` (ref: `llnocwyqvhgwpdjcqqyw`) **URL**: `https://llnocwyqvhgwpdjcqqyw.supabase.co` **CLI**: `supabase 2.84.2` (brew install supabase/tap/supabase) | 단계 | 결과 | |---|---| | `supabase link --project-ref llnocwyqvhgwpdjcqqyw` | ✅ | | `config.toml` db.major_version 15→17 | ✅ (원격 매치) | | `supabase db push --include-all` | ✅ 8개 마이그레이션 전부 적용 | | `supabase functions deploy` | ✅ 10개 Edge Functions 배포 | | `supabase secrets list` | SUPABASE_* 자동 주입 4개만 존재 | **배포된 Edge Functions** (10개) `embed-chunks`, `llm-proxy`, `search-knowledge`, `send-push`, `stripe-checkout`, `stripe-portal`, `stripe-webhook`, `stt-proxy`, `team-accept`, `team-invite` **로컬 env 파일** (git 정책으로 Claude가 직접 쓰지 못함 — 사용자가 수동 생성) - `apps/web/.env.local` — NEXT_PUBLIC_SUPABASE_URL + ANON_KEY + SITE_URL + SERVICE_ROLE_KEY - `server/supabase/.env.local` — REF/URL/KEY/DB_PASSWORD/DATABASE_URL 참조용 ## Edge Function Secrets — 필요한 외부 키 지금 배포된 함수가 실제로 동작하려면 아래 키를 `supabase secrets set ...`로 주입해야 함. 키가 없으면 해당 함수는 400/500 반환. | 키 | 쓰는 곳 | 필수? | |---|---|---| | `ANTHROPIC_API_KEY` | llm-proxy (채팅/액션/문서생성) | **필수** — 없으면 웹 /chat, /actions, meetings 문서 생성 불가 | | `OPENAI_API_KEY` | embed-chunks, search-knowledge | **필수** — knowledge 시맨틱 검색에 필수 | | `RESEND_API_KEY` | team-invite (이메일 발송) | 팀 초대 기능 쓸 때 | | `RESEND_FROM` | team-invite 보낸이 | 기본값: `D3RO Voice ` | | `SITE_URL` | team-invite 이메일 내 링크 | 기본값: `https://d3ro.dev` | | `STRIPE_SECRET_KEY` | stripe-checkout/portal | 결제 기능 쓸 때 | | `STRIPE_PRICE_PRO` / `STRIPE_PRICE_TEAM` | stripe-checkout | 각 요금제 Price ID | | `STRIPE_WEBHOOK_SECRET` | stripe-webhook | 웹훅 서명 검증 | | `GOOGLE_CLOUD_STT_KEY` | stt-proxy | 현재 스캐폴딩 상태 (index.ts 주석처리) | 주입 예: ```bash cd server SUPABASE_ACCESS_TOKEN= supabase secrets set \ ANTHROPIC_API_KEY=sk-ant-... \ OPENAI_API_KEY=sk-proj-... \ RESEND_API_KEY=re_... ``` ## 현재 단계 **V1 완료** → **V2 1차 전 페이즈 + 2차/3차/4차/5차 고도화 완료** → **Mac 부트스트랩 + Supabase 배포 완료** V2 1차: - Phase V2-1 ✅ (Monorepo 전환 a/b/c/d + minor 정리) - Phase V2-2 ✅ 로컬 + 원격 배포 완료 (Supabase llnocwyqvhgwpdjcqqyw) - Phase V2-3 ✅ (Web App MVP — Next.js 15, 12 라우트) - Phase V2-4 ✅ (데스크톱 Supabase push 동기화) - Phase V2-5 🟡 로컬 완료 (Mac 빌드), 사용자 검증 대기 - Phase V2-6 ✅ (Mobile MVP — Expo) - Phase V2-7 ✅ (팀 기능 — Web /teams) - Phase V2-8 ✅ (결제 스캐폴딩 — Stripe Edge Functions + /billing) V2 2차 고도화 (2026-04-09): - [A1] ✅ SettingsModal에 CloudSyncSection 통합 - [A2] ✅ web Sidebar 공유 layout (app/(app)/) - [A3] ✅ 11개 locale 번역 - [B] ✅ V2-4b pull 동기화 — history/dictionary - [C] ✅ V2-8b Stripe webhook HMAC-SHA256 + Customer Portal - [D] ✅ V2-6b packages/ui-native 패키지 V2 3차 고도화 (2026-04-10): - [E] ✅ V2-4b pull 확장 — meetings/meeting_memos/meeting_documents - [F] ⚠️ Database 제네릭 — interface→type 전환 (4차에서 완전 복원) - [G] ✅ apps/mobile UI를 @d3ro/ui-native로 실제 교체 - [H] ✅ V2-7b 이메일 invite flow (team_invites + Edge Functions + /accept-invite) - [I] ✅ Realtime transcripts 구독 (LiveTranscriptList) - [J] ✅ api-client 유닛 테스트 + web E2E 스모크 스캐폴딩 V2 4차 고도화 (2026-04-10): - [K] ✅ @supabase/ssr 0.5→0.10, supabase-js 2.45→2.103 bump → Database 제네릭 완전 주입 - [L] ✅ V1 Voice Conversation → web /chat - [M] ✅ V1 RAG → web /knowledge - [N] ✅ team-invite Resend 이메일 발송 - [O] ✅ mobile Expo Push - [P] ✅ meetings/[id] 문서 생성 버튼 V2 5차 고도화 (2026-04-10): - [Q] ✅ pgvector + knowledge 시맨틱 검색 — embedding 컬럼(1536차원), ivfflat 인덱스, match_knowledge_chunks RPC, embed-chunks/search-knowledge Edge Functions, /knowledge 검색창 - [R] ✅ /chat SSE 스트리밍 — llm-proxy가 Anthropic stream 프록시, chat-panel에서 content_block_delta 파싱 후 progressive 렌더링 - [S] ✅ 회의 문서 편집 DocumentEditor — MUI Dialog + textarea 기반 MarkdownEditor, 저장/삭제 - [T] ✅ V1 VoiceAction → web /actions — 자연어 명령 LLM 파싱 (create_meeting/search_knowledge/create_memo/send_team_invite), 파싱 결과 확인 후 실행 - [U] ✅ Desktop CloudSyncService Realtime 구독 + web record 오디오 Storage 업로드 + 회의 메모 작성 UI (MemoForm) - [V] ✅ 11개 locale에 nav.chat/knowledge/actions 키 추가, ko.json 정리, Sidebar Actions 메뉴 V2 6차 고도화 (2026-04-11): - [W] 🐛 fix: AppRouterCacheProvider Emotion key `'d3ro-mui'` → `'mui'` (`d3ro` 안에 숫자 `3` → Emotion 검증 실패로 dev SSR 500) - [A] ✅ Dark/Light/Theme 토글 — `theme-mode-context.tsx` (localStorage + prefers-color-scheme), Sidebar에 6종 + auto MUI Select, 12개 locale `theme.*` 키 추가 - [B] ✅ DocumentEditor Mermaid + Markdown 렌더 — `markdown-preview.tsx` (react-markdown + remark-gfm + lazy mermaid), Edit/Preview Tabs - [C] ✅ 회의 오디오 재생 — `meeting-audio-player.tsx` (Storage signed URL 1h TTL → `