# Session Handoff — 2026-04-11 (빅뱅 사이클 완료) > From: macOS (yunchan-ui-MacBookPro) > Last commit: `ceadf1b` — feat(desktop): SaaS [6] 로컬 우선 sync + login_required UI (빅뱅 Phase 3) > Branch: `main` (origin 기준 19 commits ahead) > Remote: `https://gitlab.twentyoz.kr:8443/d3r0/voice.git` ## ✅ 이번 세션에서 한 일 — 빅뱅 1차 완료 ### 비즈니스 모델 확정 - **무료 로컬 = 사용자 entry point** (회원가입 없이 즉시 킬러 피처 체험, onboarding funnel 입구) - **유료 클라우드 SaaS = 수익화** (OAuth 로그인 + pro 구독 + premium LLM + 멀티 디바이스 sync) - 이전 세션 SaaS [2] OAuth 강제 게이트 **철회** ### 완료한 Phase (모든 커밋 로컬 `main`에 있음, push 미수행) **Phase 1 — 사용자별 SQLite 격리** (`816f527`) - `db/index.ts` 전면 재설계: `openForUser(userId)`, `closeCurrent()`, `getDatabase()`, `isLocalMode()` - 경로: `userData/users/${userId}/d3ro.db` (uuid) 또는 `_local` (익명) - legacy `d3ro-voice.db` → silent archive rename - CloudSyncService `_onAuthenticated(session, reason)` + `_onSignOut()` 단일 진입점 (SSOT) - signOut 시퀀스: VoiceMode cancel → MeetingMode stopRecording → Caption stop → Realtime stop → auth.signOut → token clear → LicenseService.resetToFree → closeCurrent → openLocal → auth-changed(null) emit **Phase 1.5 — 로컬 모드 = entry point** (`1a01cea`) - `LOCAL_USER_ID = '_local'` + `openLocal()` 헬퍼 - `bootstrap.ts`: `database` step 복원. 앱 시작 시 `openLocal()` 호출 → LoginScreen 없이 즉시 메인 UI - `App.tsx`: AuthGate 상태머신 완전 제거, 항상 AppLayout 렌더 - 로그아웃 시 `closeCurrent()` → `openLocal()` — 앱은 계속 로컬 모드로 동작 **Phase 2 — 온보딩** (`f1321df`) - `AppConfig.onboardingCompleted: boolean` 정식화 (types.ts + ConfigService defaults) - 타입 캐스팅 3곳 제거 (AppLayout/OnboardingModal/SettingsModal) - OnboardingModal 5 → 6 step: 환영 → 마이크 → 핫키 → Ollama → **Cloud Sync(선택)** → 완료 - Cloud Sync step은 `saasMode === true`일 때만 노출. tagline + 3 benefit + "설정에서 언제든지 로그인". **로그인 버튼 미노출** (부담 zero, 유일 OAuth 진입점은 Settings→CloudSync) - i18n ko/en `onboarding.cloud.*` 6 키 **Phase 4 — 티어 enforcement** (`46b77f1`) — 로컬 해방 + 클라우드 gate - 기존 정책 폐기: DICTATION 20/일, LLM_PROCESS 10/일, LIVE_CAPTION/VOICE_MEMO/SCREEN_CONTEXT/MEETING_SUMMARY 등의 pro/pro_plus lock 전부 삭제 - 신규 feature: `Feature.PREMIUM_LLM`, `Feature.CLOUD_SYNC` - `CLOUD_FEATURES` Set + `canUse()`에 `isLocalMode()` 감지: 익명 로컬에서 cloud feature → `login_required` - `syncFromCloud(tier)` + `resetToFree()` — CloudSyncService가 호출 - `_fetchSubscriptionTier(userId)` — Supabase `subscriptions` 테이블 조회 **정책 매트릭스** | Feature | 익명 `_local` | 로그인 free | pro | pro_plus | |---|---|---|---|---| | 로컬 16종 (DICTATION~OS_AUTOMATION) | ✅ 무제한 | ✅ 무제한 | ✅ 무제한 | ✅ 무제한 | | PREMIUM_LLM | ❌ login_required | 🔸 5/일 | 🔸 500/일 | ✅ 무제한 | | CLOUD_SYNC | ❌ login_required | ✅ | ✅ | ✅ | **Phase 3 — 로컬 우선 sync + login_required UI** (`ceadf1b`) — 재정의 - 원래 "Supabase = source of truth" 방향 철회. 로컬이 SoT, 클라우드 mirror. - `_initialSync(reason)` 신규 — `_onAuthenticated` 끝에 fire-and-forget `pushAll → pullAll` - `FeatureAccess.reason` / `UpgradePromptEvent.reason`에 `'login_required'` 추가 - `UpgradePromptModal` login_required 케이스: "로그인 필요" title + "로그인하기" 버튼 → `d3ro:open-settings` custom event (tab=cloud) - i18n ko/en `license.loginRequired.*` 3 키 **Phase 5 — 3 클라이언트 일원화 검증** → **OAuth provider 대기** (미완) **Phase 6 — wrap-up** (이 파일) ## 🚧 남은 작업 / 다음 세션 우선순위 ### 즉시 해야 할 것 (사용자 수동) 1. **Supabase Google/GitHub OAuth provider 연결**: - https://supabase.com/dashboard/project/llnocwyqvhgwpdjcqqyw/auth/providers - Google Cloud Console에서 OAuth Client ID 발급 (Web app, Authorized redirect URI: `https://llnocwyqvhgwpdjcqqyw.supabase.co/auth/v1/callback`) - Supabase 대시보드 Google provider에 Client ID/Secret 입력 2. **Edge Function secrets 주입** (사용자가 키 제공 시): - `supabase secrets set ANTHROPIC_API_KEY=... OPENAI_API_KEY=...` - 이게 없으면 PREMIUM_LLM 호출 경로 완성해도 실제 동작 안 함 3. **커밋 push 여부 결정**: - 이번 세션 신규 커밋 5개 (`816f527..ceadf1b`) + 이전 세션 커밋 포함 총 19 ahead - 사용자 확인 후 `git push origin main` ### 빅뱅 후속 (코드 작업, 다음 세션 이후) - **PREMIUM_LLM 호출 경로 구현** (Phase 3.2): - LocalLLMService에 cloud mode 추가 또는 새 PremiumLLMService - `llm-proxy` Edge Function(Anthropic/OpenAI) 호출 - `canUse(PREMIUM_LLM)` gate + `consumeQuota` hook - ConfigService에 "premium LLM 사용" 스위치 추가 - **auto push on write** (Phase 3.3): - VoiceMode session-completed → `pushOne('history', row)` 자동 호출 - MeetingMode 종료 → `pushOne('meetings', row)` - 실패 시 queue에 enqueue - **오프라인 WriteQueue** (Phase 3.4): - `userData/users/${userId}/write-queue.json` 디스크 영속화 - 온라인 복귀 시 자동 flush - **Phase 5 실증**: OAuth provider 연결 후 데스크톱 ↔ 웹 ↔ 모바일 데이터 일원화 검증 (체크리스트는 `memory/project_status.md` SaaS [7] 섹션) - **온보딩 10개 locale 번역** — 현재 ko/en만. ja/zh/zh-TW/es/fr/de/pt/ru/vi/th 추가 - **Database 마이그레이션 — legacy d3ro-voice.db가 있는 환경 실측** — 이번 세션에서 archiveLegacyDbIfExists는 로그인 시점에만 발동하므로, 테스트 데이터(`d3ro-voice.db`)가 있는 사용자가 실제 로그인하기 전까지는 정상 상황 아래 트리거되지 않음 ## 🔑 다음 세션 시작 시 즉시 할 일 ```bash # 1. git pull (혹시 push 했다면) git pull origin main # 마지막 커밋 ceadf1b 또는 그 이후 확인 # 2. 환경 확인 (이미 mac에 다 설치됨) node --version # v22.22.2 ls apps/desktop/sidecar/.venv/bin/python3 # exists ls apps/desktop/.env.local # gitignored, D3RO_SUPABASE_* # 3. dev 재시작 npm run dev # → LoginScreen 없이 바로 메인 UI 진입 # → 모든 기존 기능 (LIVE_CAPTION, VOICE_MEMO, MEETING_SUMMARY 등) pro lock 해제됨 # → ⌘⇧1 녹음 quota 제한 없음 # → Settings → About → "온보딩 다시 보기" 로 새 6 step 플로우 확인 가능 ``` ## ⚠ 환경 / 주의사항 - **모든 기존 사용자 데이터는 `users/_local/d3ro.db`에 있음**. 이번 세션 dev 실행 중 생성됨. - **Legacy `d3ro-voice.db`는 archiveLegacyDbIfExists()가 첫 `openForUser` 시점에서만 rename** → 로그인 전까지는 원본 위치에 남아있을 수 있음 (bootstrap의 `openLocal()`도 `openForUser('_local')`이므로 실제로는 archive 트리거됨. 실측 필요). - **OAuth provider 미연결**: `_onAuthenticated` 코드는 준비됐지만 로그인 버튼 누르면 Supabase가 400 에러 반환할 것. Phase 5 실증 전 provider 연결 필수. - **기존 pro 락이 걸려있던 테스트들**: LicenseService 단위 테스트가 있다면 회귀 가능. 일단 typecheck/build는 통과. ## 📦 의존성 / 도구 - Node 22.22.2 (brew) - Python 3.13 + venv at `apps/desktop/sidecar/.venv` - Supabase CLI 2.84.2 - Ollama (사용자 설치, qwen3:4b 모델) - Electron 33.4.11 ## 🧪 마지막 검증 | 게이트 | 상태 | |---|---| | desktop `tsc --noEmit` | ✅ | | desktop `npm run build` | ✅ | | dev 런타임 cold start | ✅ `local database opened: users/_local/d3ro.db` → `Main window shown` | | 파일시스템 `users/_local/d3ro.db` | ✅ 생성 확인 | | 온보딩 UI 새 플로우 | ✅ 사용자 확인 "잘 나온다" | | OAuth 로그인 실측 | ❌ provider 미연결 | | Phase 4 로컬 해방 런타임 실측 | ⚠ 사용자가 dev 재시작 후 기존 pro lock 기능 써봐야 확정 | | 커밋 push | ❌ 사용자 컨펌 대기 | ## 📊 누적 통계 (이번 세션) - 신규 커밋: 5개 (`816f527`, `1a01cea`, `f1321df`, `46b77f1`, `ceadf1b`) - 총 변경: 16 파일 (코드 10 + i18n 4 + memory 1 + types 1) - 로컬 19 commits ahead of origin/main ## 🔗 참고 - `memory/project_status.md` — 전체 현황 + 빅뱅 Phase 1~6 상세 - `memory/big-bang-prompt.md` — 원래 빅뱅 계획 (일부 철회됨 — SaaS [2] OAuth 강제 게이트 철회, Phase 3 재정의) - `memory/handoff-archive/2026-04-11-0940-prior-bigbang.md` — 이번 세션 시작 시점 스냅샷 - `docs/v2/phase-V2-4.md` — 데스크톱 sync 원래 설계 (Phase 3 재정의 이전) - `apps/desktop/.env.local` — 빌드 타임 Supabase env (gitignored) ## 🎯 성공 기준 진행률 | # | 기준 | 상태 | |---|---|---| | 1 | 신규 사용자: LoginScreen 없이 바로 메인 UI → 로컬 기능 즉시 사용 | ✅ (Phase 1.5) | | 2 | 온보딩 자동 표시 + Cloud Sync 티저 (로그인 부담 zero) | ✅ (Phase 2) | | 3 | 핫키 ⌘⇧1 녹음 → 로컬 DB 저장, quota 제한 없음 | ✅ (Phase 4 정책) — 실측 대기 | | 4 | 로컬 기능 16종 전부 free 사용 가능 (pro/pro_plus lock 해제) | ✅ (Phase 4 정책) — 실측 대기 | | 5 | 다른 디바이스 로그인 시 같은 데이터 | ❌ OAuth provider 대기 | | 6 | 사용자 A → signOut → 사용자 B 로그인 시 A 데이터 안 보임 | ❌ OAuth provider 대기 | | 7 | PREMIUM_LLM / CLOUD_SYNC 접근 시 login_required UI | ✅ (Phase 3) — 실측 대기 | | 8 | Free 사용자 PREMIUM_LLM 5/일 quota 초과 차단 | ⚠ feature 정의만, 호출부 미구현 |