Commit graph

61 commits

Author SHA1 Message Date
Yun Chan
a8a1d6e546 feat(ui): Midnight Glass v2 전면 리디자인 — 기반+셸+대시보드
- theme.ts 재작성: glass/gradient/glow 토큰군, 기본 dark=Midnight(블루),
  6종 테마 변형 전부 파생 토큰 유지 (테마 시스템 보존)
- DS 리스킨(API 호환): 글래스 카드/버튼, Pretendard 타이포, 소프트 LED
- 신규: GradientWave(canvas 스펙트럼 음파), StatRing(컬러 링 타일)
- 보더리스: frame:false + 커스텀 TitleBar, 네이티브 스크롤바 제거(폭 불변)
  + OverlayScrollbars 오버레이 스크롤
- AppLayout 와이드 사이드바(lucide, 그라디언트 액티브 필), 대시보드 재구축,
  StatusBar(브랜드/로컬시간), Pretendard 번들, i18n +20키(ko/en)
2026-07-21 20:14:15 +09:00
Yun Chan
f3ca0eafd4 docs: v0.1.5-alpha 릴리스 + 설치 앱 전달 확인 2026-07-21 19:12:39 +09:00
Yun Chan
cbed451209 feat(icon)+fix(audio): 앱 아이콘 적용 + 트레이 아이콘 + 마이크 테스트 조기 종료 수정
Some checks failed
Build macOS / Build & Package (macOS) (push) Failing after 5s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
- 앱 아이콘: icon.svg 마스터(d3ro 브랜드 — 메탈 섀시+오렌지 웨이브) → png/ico
  생성 스크립트(generate-icons.mjs, sharp) + electron-builder win/mac 연결
- 트레이: createEmpty() 빈 아이콘 → 실제 앱 아이콘 (getAppIconPath)
- 마이크 테스트: 초기 무음 level:0을 종료로 오인하던 조기 종료 버그 —
  AUDIO.TEST_LEVEL에 done 플래그 신설(SSOT), STOP 버튼이 실제 캡처 중지,
  testDevice 실패 시 상태 롤백
2026-07-21 19:00:23 +09:00
Yun Chan
26b3fd1f63 docs: v0.1.4-alpha 릴리스 + 자동 업데이트 전달 확인 2026-07-21 18:14:09 +09:00
Yun Chan
0b9e67afe9 fix(onboarding): 무진행 구간 UX + 중복 실행 가드
Some checks failed
Build macOS / Build & Package (macOS) (push) Failing after 3s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
- verifying/manifest 상태(바이트 진행률 없음)에서 인디터미넌트 바 + 친화적 문구
  — 9.6GB 검증 ~2분간 멈춘 것처럼 보이던 문제 해소
- OnboardingModal 중복 실행 가드 (runningRef)
- LocalLLMService.pullModel: 동일 모델 동시 pull은 기존 promise 합류
- LocalSTTService.downloadModel: /download 409는 실패가 아닌 기존 진행 합류
2026-07-21 18:02:26 +09:00
Yun Chan
64c1d3709b docs: v0.1.3-alpha 릴리스 완료 — Ollama 119MB, 설치파일 225MB, 차등 업데이트 27MB 검증 2026-07-21 16:45:27 +09:00
Yun Chan
4fb1738160 fix(bundle): Ollama v0.5.7 → v0.32.1 — gemma4:e4b pull 412 해소
Some checks failed
Build macOS / Build & Package (macOS) (push) Failing after 5s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
- 번들 v0.5.7이 gemma4 요구(0.20+) 미달 → 온보딩 다운로드 412 실패
- 다운로드 스크립트: .ollama-version 스탬프로 runner 캐시 무효화
- cuda*/rocm* 재귀 가지치기 (신구 zip 레이아웃 모두 대응) + 1.5GB 경고
- darwin: v0.6+ tgz 자산 복귀 (bin/ollama·루트 바이너리 모두 지원)
2026-07-21 16:32:58 +09:00
Yun Chan
b73317d584 docs: v0.1.2-alpha Mac 포함 멀티플랫폼 릴리스 + 자동 업데이트 라이브 검증 기록 2026-07-21 16:26:37 +09:00
Yun Chan
830c0281a8 docs: v0.1.1-alpha 실배포 성공 기록 — 파이프라인/E2E 검증 결과 2026-07-21 16:06:11 +09:00
Yun Chan
3beba99668 feat(release): switchboard 릴리스 체계 이식 — Mac 배포 + Registry 게시 + 자동 업데이트
- package-macos CI job: arm64 무서명(ad-hoc) dmg+zip, D3RO_MAC_RUNNER 변수 게이트
- sync-version.mjs: 태그 → package.json 버전 동기화
- publish-gitlab-release.mjs: Generic Package Registry(버전별+latest) 업로드 + Release 생성
- electron-builder: publish generic(latest.yml 생성), 공백 없는 artifactName, mac arm64 단일화
- UpdateService: electron-updater 4h 주기 체크 + 재시작 다이얼로그 (update-feed.ts SSOT)
- @rollup/rollup-win32-x64-msvc → optionalDependencies (mac npm ci EBADPLATFORM 해소)
- docs/deployment/release-guide.md: runner 등록·feed 설정·파일명 규칙 가이드

리뷰 워크플로(3관점 적대적 검증)로 확정된 결함 8건 반영:
publish 부재로 latest.yml 미생성, 파일명 공백 vs 레지스트리 404,
mac EBADPLATFORM, needs optional의 실패 미커버, DELETE 권한 의존,
CHANGELOG regex 경계, --arm64 무시, 다이얼로그 parent 부재
2026-07-21 12:43:16 +09:00
Yun Chan
8f7d300b89 feat(conversation): OpenAI gpt-realtime-2.1 라이브 음성 대화 Premium 백엔드
- realtime-token Edge Function: 티어 검증 + realtime_session 쿼터 + ephemeral key 발급
- useRealtimeConversation 훅: WebRTC 직결 (마이크 → OpenAI, 오디오 자동 재생)
- VoiceConversationPage: conversationBackend 분기 + 연결 실패 시 로컬 fallback
- AppConfig.conversationBackend + SettingsModal 음성 대화 엔진 선택
- ErrorCode 799 ConversationRealtimeTokenFailed, i18n ko/en
2026-07-21 12:10:13 +09:00
Yun Chan
983c60cda2 feat(bootstrap): Whisper large-v3-turbo 기본 전환 + 온보딩 2단계 다운로드 진행률
- 기본 STT 모델 base → large-v3-turbo (6배 빠름, 1.6GB)
- 사이드카: /download, /download/status, /download/cancel + --models-dir
- LocalSTTService: downloadModel/cancelDownload + download-progress 이벤트
- IPC: 설계서 02의 stt:downloadModel/cancelDownload/downloadProgress 구현
- OnboardingModal: LLM(gemma4:e4b) → STT(turbo) 2단계 순차 다운로드 UI
- SettingsModal turbo 선택지 + settings.model.largeTurbo 12 locale
- 테스트: 모노레포 잔재 import 수정 (src/shared → @d3ro/core), 41/41 통과
2026-07-21 11:59:49 +09:00
yunchan8804
e947d90644 docs: 세션 래핑 — v0.1.0-alpha 배포 성공 + 설치 성공 기록 + 다음 작업 2026-04-15 23:06:51 +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
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
윤찬
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
윤찬
8467ecbdb5 chore: project_status 갱신 — Phase V2-6 Admin CRM 고도화 완료 2026-04-12 21:33:17 +09:00
윤찬
3ef971e0fd chore: project_status 갱신 — Admin CRM 독립 프로젝트 완료 2026-04-12 20:55:58 +09:00
윤찬
daed9f90d5 chore: project_status 갱신 — 마스터플랜 4개 작업 전체 완료 2026-04-12 20:26:46 +09:00
윤찬
1d6d9bded3 chore: project_status 갱신 — 라이선스 UI 빅뱅 완료 2026-04-12 19:54:25 +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
윤찬
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
윤찬
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
윤찬
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
윤찬
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
윤찬
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
윤찬
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
윤찬
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
윤찬
e55687d298 fix(desktop+supabase): 로컬 ID UUID 통일 + Realtime publication (빅뱅 Phase 5 Part 2)
SaaS [9] — 실증 A/B/C/D 전부 통과, 로컬→클라우드 push 최초 성공(pushed=1).

## Bug 4: 로컬 nanoid PK vs Supabase UUID PK 불일치
- 증상: Push history failed: invalid input syntax for type uuid: "fvy6bIzr..."
- 원인: 로컬 drizzle schema는 text PK + nanoid() 생성, Supabase는 uuid PK.
  빅뱅 사이클 내내 push가 한 번도 성공한 적 없었음 (지난 pushed=0은 데이터 0건이라서).
- 픽스: 로컬을 UUID로 통일 (근본 해결, 땜질 금지).
  14개 서비스 20곳 nanoid() → crypto.randomUUID() 일괄 교체.
  nanoid 의존성 + electron.vite.config exclude 제거.
  drizzle schema는 text PK 그대로 유지 (SQLite는 UUID 문자열 저장 가능).

## Bug 5: supabase_realtime publication 누락
- 증상: 로그인 직후 Realtime 채널 상태: TIMED_OUT
- 원인: initial_schema.sql이 transcripts 테이블만 publication에 추가.
  데스크톱이 구독하는 meetings/history/dictionary는 누락 → postgres_changes 흐르지 않음.
- 픽스: 20260411000002_realtime_publication.sql 신규.
  pg_publication_tables 카탈로그 체크 + 조건부 ADD TABLE (meetings/meeting_memos/
  meeting_documents/history/dictionary 5개). supabase db push 적용.

## Bug 6: persistSession:false에서 realtime.setAuth 자동 전파 안 됨 (부분 픽스)
- 픽스: CloudSyncService.startRealtime()에 client.realtime.setAuth(access_token)
  명시 호출 (채널 구성 이전).
- ⚠️ Bug 5+6 적용 후에도 Realtime 여전히 TIMED_OUT. 후속 조사 필요.
  블로커 아님 — 주기 pull + Phase 3.3 auto push로 최종 일관성 유지.

## 실증 결과
- A 세션 자동 복원: Restored session for yunchan8804@gmail.com → DB 재오픈
- B push 경로: HistoryService created 56a767ac-... → Sync complete pushed=1 errors=0
- C 로그아웃 복귀: Realtime 종료 → users/_local/d3ro.db 복귀 → local mode
- D 재로그인 복원: 실증 A의 restore 경로와 동일, 같은 uuid DB 파일 보존
- E 웹 크로스 디바이스: Phase 3.3 이후로 지연 (Realtime 이슈 별건)

검증: desktop tsc --noEmit , dev 재기동 , push 최초 성공 
2026-04-11 18:46:22 +09:00
윤찬
9498380f7f fix(desktop+supabase): OAuth 로그인 완주 + RLS 재귀 픽스 (빅뱅 Phase 5 Part 1)
빅뱅 Phase 5 실증 중 발견한 3개 버그 해결:

1. implicit flow 응답 무시:
   - supabase-js가 persistSession:false 일 때 PKCE code_verifier 저장 못해
     implicit flow로 fallback, fragment(#access_token=...&refresh_token=...)로
     토큰 전달. handleDeepLink는 ?code=만 파싱하고 있어 무시됨.
   - handleDeepLink에 fragment parser 추가 (access_token + refresh_token)
   - CloudSyncService.handleAuthTokens() 신규 — setSession() 후
     기존 _onAuthenticated SSOT로 수렴
   - 에러 query/fragment(error, error_description) 상세 로깅
   - 토큰 값은 로그에 안 찍히도록 (query=yes/no, fragment=yes/no 만)

2. Supabase RLS 무한 재귀:
   - team_members_read_same_team 정책이 자기 테이블 재조회 →
     Postgres RLS 엔진 무한 재귀 탐지 에러
   - meetings / meeting_memos / meeting_documents / team_invites 정책이
     team_members 서브쿼리 경유해서 전부 같이 터짐 + Realtime TIMED_OUT
   - migration 20260411000001: SECURITY DEFINER 함수 2개
     (user_team_ids, user_admin_team_ids) 신규 — Supabase 권장 패턴
   - 영향 정책: team_members(4) + meetings(2) + meeting_memos(1) +
     meeting_documents(2) + team_invites(1) 전부 함수 기반으로 재작성
   - supabase db push 완료

3. LoginScreen stale 번들:
   - Phase 1.5에서 import 제거했는데도 vite HMR/cache 어딘가에서
     stale state 유지해서 렌더러에 계속 뜸
   - LoginScreen.tsx 파일 자체 삭제 (vite 컴파일 대상 제거)

로그인 성공 확인: yunchan8804@gmail.com 으로 Google OAuth 완주 →
users/7da3dd02-9f2f-4ee9-a9b3-1c2c24875a93/d3ro.db 생성 → Initial sync 시작.
2026-04-11 11:55:42 +09:00
윤찬
817b4a2580 chore: 빅뱅 사이클 1차 wrap-up — 핸드오프 + Phase 5 체크리스트
빅뱅 Phase 1~6 완료 (Phase 5는 OAuth provider 연결 대기):
- Phase 1: 사용자별 SQLite 격리 (816f527)
- Phase 1.5: 로컬 모드 entry point (1a01cea)
- Phase 2: 온보딩 + Cloud Sync 티저 (f1321df)
- Phase 4: 티어 enforcement 로컬 해방 (46b77f1)
- Phase 3: 로컬 우선 sync + login_required UI (ceadf1b) [재정의]
- Phase 5: 3 클라이언트 일원화 검증 → OAuth provider 대기
- Phase 6: wrap-up (이 커밋)

handoff-latest 새로 작성, 이전 스냅샷은 archive로 이동.
project_status에 Phase 5 OAuth 연결 가이드 + 검증 체크리스트 기록.
2026-04-11 10:37:40 +09:00
윤찬
ceadf1b6f3 feat(desktop): SaaS [6] 로컬 우선 sync + login_required UI (빅뱅 Phase 3)
Phase 3 재정의: Supabase = source of truth 방향 철회.
로컬 entry point 철학 하에 "로컬이 SoT, 클라우드는 로그인 시 mirror"로 확정.

CloudSyncService._initialSync():
- _onAuthenticated 끝에 fire-and-forget 호출
- pushAll → pullAll 순차 실행
- signin: 익명 로컬 데이터를 사용자 계정으로 업로드
- restore: 다른 기기 변경 반영
- 실패 시 warn만, 수동 Sync 버튼으로 재시도 가능

types.ts:
- FeatureAccess.reason: 'login_required' 추가
- UpgradePromptEvent.reason: 'login_required' 추가

LicenseService:
- consumeQuota(): login_required 케이스 — promptUpgrade + D3ROError(TierRequired)
- promptUpgrade(): login_required 시 requiredTier='free'
  (로그인만 하면 free로 바로 사용 가능)
- quota_exceeded 시 현재 tier 상위로 승격 제안

UpgradePromptModal:
- isLoginRequired 분기 — title/desc 교체
- Primary button: "로그인하기" → d3ro:open-settings event (tab=cloud)

i18n ko/en:
- license.loginRequired.title/desc/signIn 추가
2026-04-11 10:35:17 +09:00
윤찬
46b77f1118 feat(desktop): SaaS [5] 티어 enforcement — 로컬 해방 + 클라우드 gate (빅뱅 Phase 4)
비즈니스 모델 확정:
- 익명 로컬 사용자 → 로컬 기능 16개 전부 무제한 (entry point)
- 클라우드 기능 (PREMIUM_LLM, CLOUD_SYNC) → 로그인 필수
- 로그인 free → PREMIUM_LLM 5/일, CLOUD_SYNC 허용
- 로그인 pro → PREMIUM_LLM 500/일 (fair use cap)
- 로그인 pro_plus → PREMIUM_LLM 무제한

기존 정책 폐기:
- DICTATION 20/일 quota 삭제
- LLM_PROCESS 10/일 quota 삭제
- LIVE_CAPTION/VOICE_MEMO/SCREEN_CONTEXT/VOICE_COMMAND/LLM_CHAIN
  pro lock 해제 → free
- FILE_TRANSCRIPTION/VOICE_CONVERSATION/DICTATION_TEMPLATE/
  MEETING_SUMMARY/LOCAL_RAG/OS_AUTOMATION pro_plus lock 해제 → free

types.ts:
- Feature enum: PREMIUM_LLM, CLOUD_SYNC 추가
- FeatureAccess.reason: 'login_required' 추가

LicenseService.ts:
- QUOTA_LIMITS / FEATURE_MIN_TIER 전면 재작성
- CLOUD_FEATURES Set 신규
- canUse(): CLOUD_FEATURES && isLocalMode() → login_required 조기 반환
- syncFromCloud(tier): Supabase subscriptions 티어를
  electron-store에 캐시 + tier-changed emit
- resetToFree(): 로그아웃 시 pro/pro_plus 캐시 + 라이센스 필드 전부 null
- isLocalMode() import 추가

CloudSyncService.ts:
- _fetchSubscriptionTier(userId): subscriptions 테이블에서 활성 티어 조회
  (row 없음 / 비정상 값 → free 폴백)
- _onAuthenticated: DB open → tier fetch → syncFromCloud → auth-changed → Realtime
- _onSignOut: Voice/Meeting/Caption stop → Realtime stop → auth.signOut →
  token clear → license.resetToFree → closeCurrent → openLocal → emit null

검증:
- desktop tsc --noEmit 
- desktop npm run build 
- 로컬 사용자 모든 기존 기능 접근 가능 (pro lock 완전 제거)
2026-04-11 10:23:50 +09:00
윤찬
f1321df83c feat(desktop): SaaS [4] 온보딩 — Cloud Sync 티저 step + AppConfig 타입 정리 (빅뱅 Phase 2)
AppConfig 정식화:
- packages/core types.ts: onboardingCompleted: boolean 추가
- ConfigService CONFIG_DEFAULTS 동기화
- 타입 캐스팅 3곳 제거 (AppLayout/OnboardingModal/SettingsModal)

OnboardingModal 6 step 확장:
- 기존: 환영 → 마이크 → 핫키 → Ollama → 완료
- 신규: 환영 → 마이크 → 핫키 → Ollama → Cloud Sync(선택) → 완료
- saasMode === true 일 때만 Cloud Sync step 노출
- Step 내용: tagline + 3 benefit (sync/premium LLM/team share)
  + "설정에서 언제든지 로그인" 안내
- 온보딩 scope 안에서는 로그인 버튼 미노출 — 부담 제로 유지
  (Settings의 CloudSyncSection이 유일한 OAuth 진입점)

i18n:
- ko/en에 onboarding.cloud.* 6개 키 추가
- 나머지 10개 locale은 추후 일괄 (Phase 2 scope 밖)

의도적 제외:
- Supabase profiles.onboarded_at — OAuth provider 설정 전까진 의미 없음
- OAuth 후 환영 페이지 — Phase 4 티어 enforcement와 함께
- STT 모델 선택 — sidecar가 자동 다운로드
2026-04-11 10:16:06 +09:00
윤찬
1a01cea33c feat(desktop): SaaS [3.5] 로컬 모드 = entry point (빅뱅 Phase 1.5)
비즈니스 모델 수정:
- 무료 로컬 모드 = 사용자 onboarding funnel 입구 (킬러 피처, 회원가입 0)
- 유료 클라우드 SaaS = 수익화 (OAuth 로그인 + pro + premium 모델 + sync)
- 이전 세션 SaaS [2] OAuth 강제 게이트 철회

db/index.ts:
- LOCAL_USER_ID = '_local' 상수 export
- openLocal() 헬퍼 — userData/users/_local/d3ro.db
- isLocalMode() 헬퍼

bootstrap.ts:
- database step 복원 (critical). openLocal() 호출로 앱 시작 시
  로컬 DB 자동 오픈 — 로그인 없이 즉시 메인 UI 진입 가능

CloudSyncService._onSignOut():
- closeCurrent() 후 openLocal() 호출 — 로그아웃 시 로컬 모드 DB로 복귀
- 앱은 계속 로컬 모드로 동작 (entry point 철학)

App.tsx:
- AuthGate 상태머신 제거 (loading/login-required/authenticated/legacy)
- LoginScreen import 제거, 항상 AppLayout 렌더
- LoginScreen.tsx 파일은 유지 (추후 Welcome 화면 재활용 가능)

검증:
- desktop tsc --noEmit 
- desktop build 
- dev 런타임: [bootstrap] local database opened → Main window shown
- users/_local/d3ro.db 파일 생성 확인
2026-04-11 10:08:35 +09:00
윤찬
816f527592 feat(desktop): SaaS [3] 사용자별 SQLite 격리 (빅뱅 Phase 1)
db/index.ts 전면 재설계:
- initDatabase/closeDatabase 폐기
- openForUser(userId), closeCurrent, getDatabase, getCurrentUserId 신 API
- 경로: userData/users/${userId}/d3ro.db
- applySchema() 추출 — 새 user DB마다 테이블 + ALTER 마이그레이션 재실행
- legacy d3ro-voice.db는 silent archive (archive-legacy-ISO.db로 rename만)

CloudSyncService 인증 lifecycle SSOT:
- _onAuthenticated(session, reason) 단일 진입점
  init() 세션 복원, handleAuthCallback() 신규 로그인 모두 수렴
  DB open 실패 시 세션 롤백 + sync-error emit
- _onSignOut() 안전 종료 시퀀스
  VoiceMode cancel → MeetingMode stopRecording → Caption stop →
  Realtime stop → auth.signOut → token clear →
  auth-changed(null) emit → closeCurrent
- VoiceMode/MeetingMode/Caption은 dynamic import로 순환 의존 회피

bootstrap.ts:
- database step 제거 (DB는 _onAuthenticated에서만 열림)
- cloud-sync step이 마지막 유지 (세션 복원 → DB open → auth-changed)
2026-04-11 09:58:00 +09:00
윤찬
5ad1d19c3a chore: 빅뱅 사이클 wrap-up — 핸드오프 + 상세 다음 세션 프롬프트
이번 세션에서 한 일:
- Mac 부트스트랩 + Supabase llnocwyqvhgwpdjcqqyw 배포
- V2-6차 고도화 [W][A~D] (웹 테마/Mermaid/오디오/Dashboard 차트)
- V2-6차 데스크톱 안정화 [E~L] (Mac 단축키, sidecar venv, beep, paste, qwen3 등)
- SaaS [1] 빌드 타임 Supabase env 주입 (electron.vite + ConfigService)
- SaaS [2] OAuth 첫 실행 강제 게이트 (LoginScreen + AuthGate + saasMode)

다음 세션 (빅뱅 사이클):
- memory/big-bang-prompt.md: 사용자가 다음 세션에 그대로 붙여넣을 상세 프롬프트
- Phase 1: 사용자별 SQLite 격리 (필수)
- Phase 2: 회원가입/온보딩 흐름 (profiles.onboarded_at)
- Phase 3: cloud-first 정책 (write queue + Supabase source of truth)
- Phase 4: 티어 enforcement (free quota)
- Phase 5: 데스크톱/웹/모바일 데이터 일원화 검증
- Phase 6: wrap-up

핸드오프:
- 이전 handoff-latest.md를 archive/2026-04-10-2300-windows-to-mac.md로 이동
- 새 handoff-latest.md: 이번 세션 결과 + 다음 세션 시작 명령
- project_status.md: SaaS 데스크톱 클라이언트 1차 섹션 추가
2026-04-11 09:45:20 +09:00
윤찬
2c2f87d7d4 docs: V2-6차 [L] qwen3 reasoning 모드 처리 2026-04-11 09:29:06 +09:00
윤찬
a41b8a4c4b docs: V2-6차 [K] macOS Cmd+V paste 수정 2026-04-11 09:23:15 +09:00
윤찬
d7405a4203 docs: V2-6차 [F~J] 데스크톱 Mac 동작 안정화 (sidecar/keylabel/import/beep/port) 2026-04-11 09:21:18 +09:00
윤찬
d2408c135f docs: 6차 사이클 [E] 키바인딩 픽스 + SaaS 아키텍처 진단 추가
- project_status.md V2-6차 [E] entry 추가
- SaaS 아키텍처 진단 섹션 추가 (사용자 우려에 대한 코드 증거 정리)
- 데스크톱 SaaS UX 개선 후보 3가지 명시
2026-04-11 09:06:55 +09:00
윤찬
07c6d13c99 feat(V2-6차): 테마 토글 + Mermaid + 오디오 재생 + Dashboard 차트
[A] Dark/Light/6종 + Auto 테마 토글
- theme-mode-context.tsx: localStorage 영속 + prefers-color-scheme
- theme-provider.tsx: 컨텍스트 기반 동적 getTheme
- sidebar.tsx: MUI Select 토글 (PaletteIcon)
- 12개 locale theme.* 키 추가 (label/dark/light/auto/nord/solarized/catppuccin/dracula)

[B] DocumentEditor Mermaid + Markdown 렌더
- markdown-preview.tsx: react-markdown + remark-gfm + lazy mermaid
  - mermaid 코드 블록 자동 SVG 렌더
  - dark theme + d3ro 폰트
- document-editor.tsx: Edit/Preview Tabs

[C] 회의 오디오 재생
- meeting-audio-player.tsx: Supabase Storage signed URL (1h) → <audio controls>
- meetings/[id] AUDIO 카드 추가

[D] Dashboard 14일 추이 차트
- meetings-trend-chart.tsx: recharts LineChart (회의/문서)
- dashboard/page.tsx: 14일 데이터 집계 + 이번 주 stat 자동 계산

[W] fix: Emotion key 'd3ro-mui' → 'mui'
- 'd3ro-mui'에 숫자 3이 포함돼 Emotion 키 검증 실패
- dev SSR /login 500 에러 차단했음

deps:
- react-markdown@10, remark-gfm@4, mermaid@11, recharts@3
2026-04-11 08:57:27 +09:00
윤찬
162a59dc47 chore: Mac 부트스트랩 + Supabase llnocwyqvhgwpdjcqqyw 배포
- Node 22 + sox + supabase CLI 설치 (brew)
- npm install --force + @electron/rebuild@3 33.4.11 완료
- desktop/web typecheck + web build + api-client test 전부 통과
- supabase link --project-ref llnocwyqvhgwpdjcqqyw
- config.toml db.major_version 15→17 (원격 매치)
- db push: 8개 마이그레이션 전부 적용
- functions deploy: 10개 Edge Functions 전부 배포
- apps/web/.env.local + server/supabase/.env.local 생성 (gitignored)
- next build 재검증 — Supabase 런타임 주입 확인 (라우트 동적화)
2026-04-11 08:45:12 +09:00
yunchan8804
b386733d1e feat(V2-5차): pgvector 시맨틱 검색 + chat 스트리밍 + doc 편집 + VoiceAction + Realtime + storage + 메모
묶음 Q — pgvector + 시맨틱 검색:
- migrations/20260410000004_pgvector_knowledge.sql
  - vector extension, embedding vector(1536) 컬럼
  - ivfflat cosine 인덱스
  - match_knowledge_chunks(query_embedding, match_count, similarity_threshold) RPC
  - RLS: user_id 또는 소속 팀 기준
- functions/embed-chunks: 문서 소유권 확인 후 OpenAI text-embedding-3-small 배치 호출 → knowledge_chunks.embedding 업데이트
- functions/search-knowledge: 쿼리 텍스트 → OpenAI 임베딩 → user 권한 RPC 호출 → 상위 청크 반환
- config.toml에 embed-chunks/search-knowledge 등록
- components/knowledge/knowledge-search.tsx: 검색창 + 결과 카드(유사도 %)
- /knowledge 페이지에 검색 UI 추가

묶음 R — /chat SSE 스트리밍:
- functions/llm-proxy: Anthropic Messages API stream 지원
  - ANTHROPIC_API_KEY 없으면 SSE placeholder 스트림
  - stream=true일 때 response.body 그대로 프록시 (text/event-stream)
  - stream=false는 JSON 응답
- components/chat/chat-panel.tsx:
  - stream=true로 요청
  - ReadableStream 파싱 (SSE: data: {type:content_block_delta, delta:{text_delta}})
  - assistantId 메시지를 progressive 업데이트, scrollToBottom
  - 불필요한 LlmResponse 인터페이스 제거

묶음 S — DocumentEditor:
- components/meetings/document-editor.tsx
  - 문서 박스 클릭 → MUI Dialog (fullWidth, maxWidth md)
  - TextField multiline 20~40 rows, monospace
  - 제목 편집 + 저장/삭제 버튼
- meetings/[id] 페이지 Documents 섹션을 DocumentEditor로 교체 (+ typoSx 미사용 import 제거)

묶음 T — /actions (VoiceAction 이식):
- components/actions/action-runner.tsx
  - SYSTEM_PROMPT로 JSON 스키마 강제 (create_meeting/search_knowledge/create_memo/send_team_invite/unknown)
  - LLM 응답에서 JSON 추출 → 파싱 → 확인 후 실행
  - 각 type별 실행 로직 (meetings/memos INSERT, 검색은 안내만)
- app/(app)/actions/page.tsx + Sidebar Actions 메뉴 + AutoAwesomeIcon

묶음 U — Realtime + Storage + 메모 UI:
- CloudSyncService:
  - RealtimeChannel import 추가
  - startRealtime(): meetings/history/dictionary 변경 구독, debounce 후 pullAll 자동 트리거
  - stopRealtime(), signIn 직후/세션 복원 시 자동 시작, signOut 시 종료
- apps/web/components/meetings/memo-form.tsx
  - TextField + 저장 버튼, 회의 시작 기준 경과 ms 자동 계산
  - Realtime 구독자에게 자동 전파
- meetings/[id] MEMOS 섹션에 MemoForm 렌더
- apps/web/components/record/mic-recorder.tsx
  - STT 성공 후 Supabase Storage 'audio' 버킷에 {user_id}/{ts}.webm 업로드
  - meetings 테이블에 INSERT (raw_transcript, audio_storage_key, duration_ms, ended_at)
  - Storage/meetings 실패는 전사 결과는 유지하며 경고

묶음 V — 11개 locale nav 키:
- en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th 에 nav.chat/nav.knowledge/nav.actions 추가
- ko.json에 nav.actions 추가
- Sidebar에 Actions 메뉴(AutoAwesomeIcon) 등록

검증:
- desktop typecheck + build OK
- web typecheck + build OK (15 라우트: 기존 14 + /actions)
- api-client test 19 passed
- 회귀 없음

통계:
- 총 Edge Functions 12개 (embed-chunks/search-knowledge 추가)
- 총 SQL 마이그레이션 8개
- 웹 라우트 15개 (accept-invite/actions/billing/chat/dashboard/knowledge/login/meetings/meetings[id]/record/teams/teams[id]/auth-callback/root/_not-found)
2026-04-10 09:30:41 +09:00