d3ro-voice/memory/big-bang-prompt.md
윤찬 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

14 KiB

빅뱅 사이클 시작 프롬프트

사용자가 다음 세션에서 이 파일 내용을 그대로 Claude에게 붙여넣어 시작. 이 안에 모든 컨텍스트 + 단계별 작업 + 검증 기준이 들어있음.


너에게 (다음 세션 Claude에게)

D3RO-VOICE 빅뱅 사이클을 시작한다. 데스크톱을 V1 single-user fat client에서 진짜 SaaS 데스크톱 클라이언트로 완성하는 작업이다. Notion / Linear / Slack 데스크톱처럼 "내 SaaS 계정의 맥북 클라이언트"가 되도록 한다.

시작 전 반드시 할 일 (이 순서대로):

# 1. 핸드오프 + 현황 로드
/session-handoff resume

# 2. memory/handoff-latest.md 와 memory/project_status.md 처음부터 끝까지 읽기
#    그 안에 이번 세션 직전(2026-04-11)까지의 모든 진행 + 알려진 이슈 + 환경 검증 결과가 있다

# 3. CLAUDE.md (root) — 강제 규칙 13개 숙지
# 4. docs/design/00~03 — V1 설계서 (서비스 인터페이스, IPC, DB 스키마)
# 5. docs/v2/00-v2-master-plan.md + docs/v2/phase-V2-4.md — 데스크톱 ↔ Supabase 동기화 설계

아키텍처 강제 규칙 (CLAUDE.md 13개 + 본 사이클 추가):

  • 서비스 = 싱글톤 + EventEmitter
  • IPC = 설계서 02의 ipc-channels.ts 그대로
  • 에러 = D3ROError + ErrorCode enum
  • DB = drizzle-orm 스키마 그대로
  • any 절대 금지, console.log 절대 금지 (logger 사용)
  • 테마 SSOT — 색상은 d3roPalette/d3roShadow CSS var (theme.ts 외 hex 금지)
  • i18n — t() 함수만 (하드코딩 한국어/영어 금지)
  • 작업 완료 즉시 memory/project_status.md 갱신
  • 본 사이클 추가: cloud-first 정책. SQLite는 read-through cache 역할로만 운영, write는 우선 Supabase에. Supabase 실패 시 큐잉.

직전 세션 요약 (2026-04-11, 마지막 커밋 dd1c305)

이미 완료된 것 (다시 하지 말 것)

  1. Mac 부트스트랩: Node 22, sox, Supabase CLI, npm install, electron rebuild
  2. Supabase 배포: 프로젝트 llnocwyqvhgwpdjcqqyw, 8 마이그레이션 + 10 Edge Functions
  3. V2-6차 [W][A~D]: 웹 Emotion key 픽스, 테마 토글, Mermaid 렌더, 회의 오디오, Dashboard 차트
  4. V2-6차 [E~L]: 데스크톱 Mac 안정화 (단축키 표기, sidecar venv, paste, beep, qwen3 reasoning, 동적 포트 등)
  5. SaaS [1] 빌드 타임 Supabase env 주입:
    • electron.vite.config.ts loadEnv('D3RO_') + main define
    • apps/desktop/.env.local (gitignored): D3RO_SUPABASE_URL, D3RO_SUPABASE_ANON_KEY
    • ConfigService.configGet('supabaseUrl'/'supabaseAnonKey')은 빌드 타임 우선
    • configSet는 빌드 타임 모드일 때 변경 거부
  6. SaaS [2] OAuth 강제 로그인 게이트:
    • CloudSyncState.saasMode: boolean 추가
    • LoginScreen.tsx 신규 (Google/GitHub만, URL/Key 입력 0)
    • App.tsx AuthGate 상태머신 (loading | login-required | authenticated | legacy)
    • CloudSyncSection이 saasMode일 때 URL/Key 필드 숨김

알려진 이슈

  • 데스크톱 SQLite는 여전히 단일 파일 (userData/d3ro-voice/d3ro.db) — 본 사이클 [3]에서 해결
  • Ollama qwen3:4b는 reasoning model이라 CPU에서 느림. gemma3:1b 등 빠른 모델 권장.
  • Edge Function secrets (ANTHROPIC/OPENAI/RESEND/STRIPE)는 사용자가 키 안 줌 — 웹 /chat /knowledge는 키 채워지기 전 동작 안 함

🎯 빅뱅 목표

데스크톱 = SaaS 데스크톱 클라이언트. 한 PC에 여러 사용자가 OAuth로 번갈아 로그인해도 데이터가 절대 섞이지 않으며, 회원가입 → 온보딩 → 일일 사용 → 결제까지 전부 SaaS 흐름으로 일원화.


📋 단계별 작업 (Phase 1~6, 의존성 순서대로)

Phase 1: 사용자별 SQLite 격리 (필수, 데이터 안전 핵심)

현재 상태: 모든 데이터(history, dictionary, meetings, ...)가 단일 SQLite 파일에 저장. 한 PC에 사용자 A가 로그인 후 데이터 만들고, 로그아웃 후 사용자 B가 로그인하면 사용자 B가 사용자 A의 데이터를 본다.

목표: SQLite를 사용자별 디렉토리로 분리.

  • ~/Library/Application Support/d3ro-voice/users/${userId}/d3ro.db
  • 로그인 시 그 사용자의 DB로 스위치
  • 로그아웃 시 DB 닫기

구현 작업:

  1. apps/desktop/src/main/db/index.ts (또는 DatabaseService 위치) 확인 — 현재 어떻게 init되는지
  2. getDatabase()getDatabase(userId | null) 시그니처로 변경 — null이면 not initialized 에러
  3. DB lifecycle 관리자 신규 (또는 DatabaseService 강화):
    • openForUser(userId: string): Promise<void> — 새 user.db 열고 마이그레이션 적용
    • closeCurrent(): Promise<void> — 현재 DB 닫기
    • getCurrentUserId(): string | null
  4. CloudSyncService 통합:
    • signIn 성공 직후 database.openForUser(session.user.id)
    • 저장된 refresh token 복원 후 session 복구되면 openForUser(session.user.id)
    • signOutcloseCurrent() + 모든 service stop
  5. 모든 service init 타이밍 변경 — DB가 열려있을 때만 동작
    • HistoryService, DictionaryService, MemoService, MeetingModeService, ChainService, ...
    • 일부는 lazy하게 동작해도 OK, 일부는 init에서 DB 의존
    • 권장 패턴: 서비스가 getDatabase()를 호출하는데 not-initialized면 throw → caller가 catch하고 빈 결과 반환
  6. 마이그레이션 정책 — 첫 로그인 사용자에게 기존 단일 DB 데이터 귀속:
    • 첫 로그인 시점에 userData/d3ro-voice/d3ro.db가 존재하면
    • 그 사용자의 user 디렉토리로 mv (또는 copy + 원본 백업)
    • 두 번째부터 로그인하는 사용자는 빈 DB로 시작
    • dialog로 사용자에게 확인 — "기존 데이터를 이 계정으로 가져올까요?"
  7. renderer 측: AuthGate가 'authenticated' 진입 시점에 DB가 열려있다고 가정 가능하도록 main이 auth-changed emit 이전에 DB 열기 완료

위험:

  • 기존 사용자의 데이터를 잘못 마이그레이션하면 영구 손실. 백업 필수.
  • 모든 service init 순서를 다시 확인해야 함 — bootstrap.ts 순서 주의.

검증:

  • 로그인 → DB 열림 → 회의 녹음 → 데이터 저장 → 로그아웃 → DB 닫힘
  • 다시 같은 계정 로그인 → 데이터 복원 확인
  • 다른 OAuth 계정 로그인 → 빈 DB
  • 마이그레이션: legacy 단일 DB가 있을 때 첫 로그인 시 dialog 확인 후 사용자 디렉토리로 이동

Phase 2: 회원가입 / 첫 온보딩 흐름

현재 상태: LoginScreen에서 OAuth 로그인하면 바로 메인 UI 진입. 신규 사용자는 환영도 없고, 핫키 설정도 안 되어 있고, Whisper 모델 안내도 없음.

목표: 첫 로그인 사용자에게 OnboardingModal 자동 표시.

구현 작업:

  1. OnboardingModal.tsx는 이미 존재 (V1) — 재활용
  2. profiles 테이블에 onboarded_at: timestamptz | null 컬럼 추가 (Supabase 마이그레이션)
  3. 첫 로그인 후 profiles.onboarded_at이 null이면 OnboardingModal 자동 표시
  4. 완료 시 update profiles set onboarded_at = now()
  5. OnboardingModal 단계:
    • Step 1: 환영 + 마이크 권한 안내 (macOS는 첫 사용 시 다이얼로그)
    • Step 2: 핫키 설정 (HotkeyRecordModal — Mac 표기 ⌘⇧1)
    • Step 3: STT 모델 선택 (base/small/medium — 기본 base)
    • Step 4: (선택) Ollama 모델 안내 — qwen3:4b 권장하되 더 빠른 옵션도 표시
    • Step 5: 완료 화면

검증:

  • 신규 사용자 로그인 → 자동 OnboardingModal
  • 완료 후 다음 로그인 → OnboardingModal 안 뜸
  • DB의 onboarded_at 컬럼 검증

Phase 3: Cloud-first 정책 (가장 큰 변경)

현재 상태: 모든 데이터가 SQLite에 먼저 저장되고, CloudSyncService가 나중에 push.

  • 장점: 오프라인 사용 가능
  • 단점: 멀티 디바이스 sync 지연, 충돌 처리 복잡, 데이터가 SQLite에만 있는 동안 잃을 위험

목표: Supabase가 source of truth. 데스크톱은 read-through cache.

  • write: Supabase 우선 → 성공 시 local cache 업데이트 → 실패 시 큐잉 + 재시도
  • read: local cache 우선 (UI 빠름) → 백그라운드에서 Supabase pull → 차이 있으면 cache 갱신
  • 오프라인 모드: 큐가 쌓이고 온라인 복귀 시 flush

구현 작업:

  1. WriteQueue 신규 — 실패한 write를 디스크에 영속화 (userData/users/${userId}/write-queue.json)
  2. CloudSyncService 확장:
    • pushOne(table, row) — 단건 즉시 push
    • enqueueWrite(table, row) — 실패 시 큐잉
    • flushQueue() — 온라인 복귀 시 호출
  3. HistoryService 등 각 서비스:
    • 기존 addEntry(entry)가 SQLite write만 했다면 → await cloudSync.pushOne('history', entry) 추가
    • 실패 시 cloudSync.enqueueWrite(...)
    • 그 후 SQLite cache 업데이트
  4. 읽기 측:
    • 컴포넌트 진입 시 SQLite에서 즉시 표시
    • 백그라운드에서 cloudSync.pullTable('history') 실행 → 차이 발견 시 cache 갱신 + 이벤트 emit
  5. Realtime 구독 강화 — 이미 V2-5에서 시작. 다른 디바이스에서 push한 변경을 즉시 받아 cache 업데이트.

위험:

  • write latency 증가 — 사용자가 회의 종료 후 결과 보는 데까지 더 걸림. UI에서 optimistic update 필요.
  • 충돌 처리 — 같은 row를 두 디바이스에서 동시 수정 시. 일단 last-write-wins (updated_at 기준), 나중에 CRDT 검토.
  • 큐가 너무 커지면 디스크 압박 — 크기 제한 + 오래된 항목 폐기 정책.

검증:

  • 정상 흐름: 회의 녹음 → 즉시 Supabase에 INSERT 확인 (Supabase Studio)
  • 오프라인: 네트워크 끊은 상태로 녹음 → 큐 쌓임 → 네트워크 복구 → 자동 flush
  • 멀티 디바이스: 웹에서 회의 만들면 데스크톱에서 Realtime으로 즉시 보임

Phase 4: 티어 enforcement (free quota)

현재 상태: web 쪽 Edge Functions(llm-proxy, embed-chunks 등)는 requireUser + quota 체크가 있을 수 있음. 데스크톱은 LicenseService가 V1에 있지만 SaaS 티어와 동기화 안 됨.

목표: 사용자가 free 티어면 데스크톱에서도 일일 quota 넘으면 차단.

구현 작업:

  1. Supabase daily_usage 테이블 (이미 있음 from V2-2 마이그레이션) 활용
  2. CloudSyncService.getSubscription() 새 메서드 — subscriptions 테이블에서 현재 사용자 tier 가져오기
  3. LicenseService.canUse(Feature) 강화:
    • Supabase에서 tier + 오늘 daily_usage 조회
    • free + 오늘 quota 초과면 deny
  4. 각 quota 사용처에서 consume:
    • STT 호출 시 Supabase RPC increment_daily_usage('stt')
    • LLM refine 시 increment_daily_usage('llm')
    • sidecar는 로컬이지만 quota는 SaaS 차원에서 카운트
  5. 차단 시 UI:
    • UpgradePromptModal 자동 표시 (이미 V1에 있음)
    • "Free 한도 도달, /billing에서 업그레이드"

검증:

  • free 사용자로 STT 50회 → 51번째 차단
  • pro 사용자로 무제한
  • 일자 바뀌면 quota 초기화

Phase 5: 데스크톱 ↔ 웹 ↔ 모바일 데이터 일원화 검증

작업:

  1. 데스크톱에서 회의 녹음 → 웹 /meetings에서 즉시 보임 (Realtime)
  2. 웹에서 메모 추가 → 데스크톱에서 즉시 반영
  3. 모바일 Expo 앱에서도 같은 데이터 표시
  4. 회원가입 → 모든 기기에서 같은 식의 환영 흐름
  5. 결제 → 모든 기기에서 즉시 unlock

검증 시나리오:

  • 신규 사용자 가입 → 웹 → 데스크톱 → 모바일 순서로 로그인 → 같은 빈 상태
  • 데스크톱에서 회의 녹음 → 웹/모바일에서 5초 내 보임
  • 웹에서 회의 문서 편집 → 데스크톱에서 즉시 갱신

Phase 6: Wrap-up

  1. project_status.md 갱신 — 빅뱅 [1~5] 모두 완료 표시
  2. memory/handoff-latest.md 작성 (이전 것은 archive로)
  3. 빅뱅 사이클 커밋들 push 여부는 사용자 명시 후
  4. 다음 세션에 다룰 후보:
    • SQLite cache 자체를 제거하고 IndexedDB만 쓰기 (더 큰 변경)
    • 데스크톱 결제 deeplink 강화
    • Knowledge 파일 업로드 (PDF/docx 파싱 — 사용자가 키 줘야 함)
    • 모바일 기능 확장

🚨 작업 진행 시 강제 규칙

  1. 모든 변경은 하나씩 검증 — 한 phase 끝나면 typecheck + dev 띄워서 흐름 확인 후 다음
  2. DB 마이그레이션은 백업 — 사용자 데이터 손실 절대 금지. legacy DB는 archive 폴더로 옮기지 삭제 금지
  3. 커밋 단위는 phase별 — Phase 1 끝 → 커밋, Phase 2 끝 → 커밋, ...
  4. memory/project_status.md 매 phase 끝마다 갱신 (CLAUDE.md 규칙 13)
  5. 설계서 참조 최우선 (CLAUDE.md 규칙 1) — 작업 전 docs/design/01 (서비스 명세), docs/design/02 (IPC), docs/v2/phase-V2-4 (데스크톱 동기화) 확인
  6. 사용자에게 보고는 간결하게 — 코드 dump 금지, 결과 + 검증 + 다음 단계만

🤝 사용자에게 묻기 좋은 시점

다음 중 하나라도 만나면 즉시 사용자 컨펌:

  • DB 마이그레이션 실행 직전 (legacy DB → 사용자 디렉토리 이동)
  • Supabase 스키마 마이그레이션 실행 직전 (profiles.onboarded_at 컬럼 추가)
  • 큰 리팩토링이 예상보다 영향 범위 클 때
  • 외부 서비스 키가 필요할 때 (이번 사이클은 이미 박혀있는 D3RO_SUPABASE_*만 사용)

🏁 성공 기준

  • 신규 사용자가 LoginScreen → Google 클릭 → OnboardingModal → 메인 UI까지 한 번에 진행
  • 핫키 ⌘⇧1로 녹음 → 자동 paste + Supabase에 저장 (5초 내)
  • 다른 디바이스(웹) 로그인 시 같은 데이터 보임
  • 한 PC에서 사용자 A 로그아웃 → 사용자 B 로그인 시 A 데이터 안 보임
  • Free 사용자 일일 quota 도달 시 데스크톱에서도 차단

이 5가지가 다 되면 빅뱅 1차 완료. 사용자에게 체크리스트 형태로 보고.


데스크톱 .env.local 참고 (이미 박혀있는 값)

D3RO_SUPABASE_URL=https://llnocwyqvhgwpdjcqqyw.supabase.co
D3RO_SUPABASE_ANON_KEY=sb_publishable_0uo4UYYvUO2y-sVMFdYylA_hHv9qRt5

DB 비밀번호 + service_role key는 server/supabase/.env.local에 있음 (gitignored).


자, 이제 시작하자. 빅뱅이다. 🚀