[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
28 KiB
D3RO-VOICE 프로젝트 현황
마지막 갱신: 2026-04-11 (Mac 부트스트랩) 규칙 13: 작업 완료 즉시 이 파일 갱신 의무
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_KEYserver/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 <noreply@d3ro.dev> |
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 주석처리) |
주입 예:
cd server
SUPABASE_ACCESS_TOKEN=<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개 localetheme.*키 추가 - [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 →<audio controls>),/meetings/[id]AUDIO 카드 - [D] ✅ Dashboard 14일 추이 차트 — recharts LineChart (회의/문서 수 일별 집계), 이번 주 stat 자동 계산
6차 검증: web typecheck/dev 200, production build 15 라우트 (dashboard 1.6kB→111kB, meetings/[id] 4.8kB→55kB by chart/mermaid)
다음 사이클 후보: VoiceAction 카탈로그 확대, RAG → /chat 연동, Knowledge 파일 업로드(PDF/docx), Voice Input UI(/actions 마이크), 알림 센터, 팀 대시보드, recharts dynamic import로 dashboard split, Playwright browsers install + E2E 실 실행
V1 완료 페이즈
| Phase | 내용 | 상태 |
|---|---|---|
| 1~9 | 기본 음성 어시스턴트 (STT/LLM/TTS/핫키/팝업) | ✅ |
| 10 | Killer features (메모, 보이스 커맨드, 스크린 컨텍스트, 체인, 캡션) | ✅ |
| 11 | Monetization (라이선스, 쿼터) | ✅ |
| 12 | Pro features (파일 전사, 회의 요약, 딕테이션 템플릿) | ✅ |
| 13 | 음성 대화, RAG, 보이스 액션 | ✅ |
| 14 | 회의 모드 | ✅ |
| 14.5 | 회의 고도화 (다중 문서 생성/편집/내보내기) | ✅ |
| 15 | Auto Polish + AI 채팅 + 마인드맵 + 공유 | ✅ |
| 15.5 | 화자 구분 (LLM 추정 + pyannote) | ✅ |
추가 개선사항 (post-15.5)
- 히스토리/회의 목록 반응형 그리드 + 카드 UI
- 설정 LLM 모델 드롭다운
- 오디오 테스트 실시간 레벨 미터
- Ollama 미실행 시 설치 경로 탐색 후 자동 스폰 (2026-04-08)
V2 진행 상황
Phase V2-1: Monorepo + Core 추출 — 진행 중
| Sub-phase | 범위 | 상태 |
|---|---|---|
| V2-1a | npm workspaces + apps/desktop으로 V1 이동 | ✅ 완료 |
| V2-1b | packages/core 추출 (types, errors, ipc-channels, constants, utils) | ✅ 완료 |
| V2-1c | packages/ui 추출 (DS 컴포넌트 + theme + theme-vars) | ✅ 완료 |
| V2-1d | packages/i18n 추출 (locale JSON + 훅) | ✅ 완료 |
V2-1d 완료 내역
packages/i18n/(@d3ro/i18n) 신규 생성:src/locales/12개 JSON (ko/en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th)src/index.tsx: TFunction, Locale, LOCALE_META, resolveTranslation, createFormatDate/Number/RelativeDate/Time, I18nContext, useI18n, I18nProvider
- Electron 독립성 확보:
I18nStorageinterface 추가 (load/save 어댑터)- 기존
window.electronAPI.config직접 호출 →storageprop 주입 패턴 - 기본값은 noopStorage (메모리 한정)
- 기존
apps/desktop/src/renderer/App.tsx에electronI18nStorage어댑터 구현 후<I18nProvider storage={electronI18nStorage}>로 주입- apps/desktop 설정:
@d3ro/i18n: "*"dep 추가- tsconfig paths, vite alias, externalize exclude에
@d3ro/i18n추가
- 일괄 치환:
./i18n,../i18n,../../i18n→@d3ro/i18n(29 파일) apps/desktop/src/renderer/i18n/디렉토리 완전 제거
V2-1d 검증
- typecheck ✅
- build ✅
- dev 런타임 ✅ (DB/핫키/Ollama/Main window 모두 정상)
- Electron 의존이 완전히 apps/desktop에만 남아 monorepo 원칙 준수 (packages/i18n은 React에만 의존, 웹/모바일에서 그대로 재사용 가능)
V2-1c 완료 내역
packages/ui/(@d3ro/ui) 신규 생성:src/theme.ts(d3roPalette/d3roTypo/d3roShadow SSOT 전체)src/theme-vars.ts(팝업/main 프로세스용 CSS 변수 맵)src/components/ds/{CrtDisplay, InstrumentPanel, Led, MetalCard, MetalDial, PhosphorText, PhysicalButton, ScreenPanel, ButtonGroup, index}.tsxsrc/index.tsbarrel
packages/ui/package.json: React/MUI/Emotion은 peerDependencies,@d3ro/core만 직접 depapps/desktop/src/shared/디렉토리 완전 제거 (theme-vars가 마지막이었음)- subpath exports:
./theme,./theme-vars,./components/ds - DS 컴포넌트 내부
../../theme상대 경로는 그대로 유효 (packages/ui 내부에서 해결) - 일괄 치환:
../theme,../../theme,./theme→@d3ro/ui/theme../components/ds,../../components/ds,./ds,../ds→@d3ro/ui/components/ds../ds/<Component>,../../ds/<Component>→@d3ro/ui/components/ds(세부 파일 import는 barrel로 통합)@shared/theme-vars→@d3ro/ui/theme-vars(WindowManager 1건)
apps/desktop/tsconfig.node/web.json:@shared/*paths 완전 제거,@d3ro/ui,@d3ro/ui/*paths 추가,include에서src/shared/**/*제거electron.vite.config.ts:@sharedalias 제거,@d3ro/uialias 추가, externalize exclude에@d3ro/ui추가vitest.config.ts: alias 교체
V2-1c 검증
typecheck✅build✅dev런타임 ✅ (DB/핫키/Ollama/Main window 정상)apps/desktop/src/shared/디렉토리 자체가 사라져@shared/*는 이제 프로젝트에 존재하지 않음
V2-1b 완료 내역
packages/core/패키지 신규:@d3ro/core, subpath exports (types/errors/ipc-channels/constants/utils/*), docx dep 포함- 6개 파일
git mv로packages/core/src/로 이동:apps/desktop/src/shared/{types,errors,ipc-channels,constants}.tsapps/desktop/src/main/utils/{meeting-markdown,markdown-to-docx}.ts
packages/core/src/index.tsbarrel export 추가apps/desktop/package.json에"@d3ro/core": "*"dep 추가apps/desktop/tsconfig.node.json,tsconfig.web.json의paths에@d3ro/core/*추가,@shared/*는 theme-vars용으로 유지apps/desktop/electron.vite.config.tsalias 3개 섹션에@d3ro/core추가 + M1 수정:resolve('src/shared')→resolve(__dirname, 'src/shared')externalizeDepsPluginexclude에@d3ro/core추가 (main/preload 둘 다)apps/desktop/vitest.config.ts에@d3ro/corealias 추가- 일괄 치환:
@shared/{types,errors,ipc-channels,constants}→@d3ro/core/*(사용자 79개 파일, 167건)- static import (
from '@shared/...') - dynamic import (
import('@shared/...')) - type expression import (
keyof import('@shared/...').AppConfig)
- static import (
MeetingModeService.ts의 상대 경로 utils import (../utils/meeting-markdown,../utils/markdown-to-docx)를@d3ro/core/utils/*로 교체packages/core내부 자기 참조:meeting-markdown.ts의@shared/types→../types상대 경로
V2-1b 검증
npm run typecheck✅npm run build✅ (main+preload+renderer)npm run dev런타임 실행 ✅ (DB/핫키/Ollama 모두 정상)apps/desktop/src/shared/에는theme-vars.ts1개만 남음 (V2-1c에서 이동 예정)
V2-1a 완료 내역
- 루트
package.json을 npm workspaces 루트로 재구성 (workspaces: apps/, packages/) turbo.json,tsconfig.base.json추가 (Turborepo 자체 설치는 뒤로 미룸)- V1 파일 전체를
apps/desktop/으로git mv:- src/, resources/, tests/, sidecar/, scripts/
- electron.vite.config.ts, electron-builder.yml, vitest.config.ts
- tsconfig.node.json, tsconfig.web.json (+ 새 tsconfig.json)
apps/desktop/package.json신규 (name: "@d3ro/desktop", 기존 deps 그대로)@electron/rebuild --version=33.4.11로 native 모듈 재빌드 (better-sqlite3, uiohook-napi)- 루트 tsconfig.json → apps/desktop references 형태
V2-1a 결정사항
- 패키지 매니저: npm workspaces 유지 (pnpm 전환은 별도 sub-phase)
- 이유: Turborepo는 npm workspaces와 호환, 구조 이동과 PM 전환을 동시에 하면 롤백 불가
- site/: 루트 유지 (랜딩 페이지, V2 무관)
- docs/, memory/, CLAUDE.md: 루트 유지
- productName: "d3ro-voice" 필수 — package name을
@d3ro/desktop으로 바꾸면 Electron의app.getName()이 바뀌어 userData 경로가%APPDATA%\@d3ro\desktop\으로 이동(기존 DB/설정 유실).productName을 명시하면app.getName()이 이를 우선 반환하여%APPDATA%\d3ro-voice\그대로 유지됨.
검증 결과
npm run typecheck✅npm run build✅ (electron-vite main+preload+renderer)npm run dev✅ 실제 실행 → DB 경로d3ro-voice/로 복원, 기존 핫키(Alt keyCode=2) 로드, Ollama 자동 실행 감지 모두 정상npm run dist(electron-builder installer)은 V2-1 완료 후 별도 검증
Phase V2-2: Supabase 인프라 — 로컬 작업 완료
작성된 파일 (server/supabase/)
config.toml— Supabase CLI 설정 (로컬 + OAuth providers)migrations/20260409000001_initial_schema.sql— 12개 테이블 (profiles, teams, team_members, meetings, meeting_memos, meeting_documents, transcripts, history, dictionary, memo_tags, daily_usage, subscriptions) + 인덱스 + FK CASCADEmigrations/20260409000002_rls_policies.sql— 개인/팀 이중 RLS 패턴migrations/20260409000003_auth_triggers.sql—handle_new_user(profiles + subscriptions 자동 생성),moddatetime(8개 테이블 updated_at 트리거),increment_daily_usage(service_role 전용 쿼터 RPC)migrations/20260409000004_storage_buckets.sql— audio/exports/avatars 버킷 + 경로 기반 정책functions/_shared/— cors.ts, auth.ts (requireUser), quota.ts (티어별 쿼터)functions/stt-proxy/index.ts— Google Cloud STT 래퍼 스캐폴딩 (placeholder 응답)functions/llm-proxy/index.ts— Anthropic Messages API 래퍼 스캐폴딩 (티어별 모델 정책)seed.sql— 개발 시드 (빈 상태).gitignore— .branches/, .temp/, .env
설계 문서
docs/v2/phase-V2-2.md— 상세 설계 (스키마/RLS/Edge Functions/실시간/Storage)docs/v2/phase-V2-2-setup.md— 사용자 액션 가이드 (Supabase 계정/OAuth/CLI/배포)
사용자 액션 대기 중 (로컬 외 작업)
- Supabase 프로젝트 생성 (https://supabase.com)
supabase login && supabase link --project-ref <REF>supabase db push(4개 마이그레이션 적용)- Google/GitHub OAuth 앱 등록 + Supabase Auth Providers 설정
- Google Cloud STT 키 + Anthropic API 키 발급
supabase secrets set+supabase functions deploy
설계 결정사항
- UUID PK: V1은 nanoid(text), V2는 PostgreSQL
gen_random_uuid(). V2-4 (동기화)에서 기존 SQLite 데이터는 nanoid 유지, 새 데이터는 UUID. - timestamptz: V1은 epoch ms(integer), V2는
timestamptz. V2-4 동기화 시 타입 변환 필요. - 멀티테넌시: 모든 테이블에
user_id. 회의 관련 테이블은team_idnullable 추가. - RLS 이중 패턴: 개인 전용(history/dictionary)은 단순
user_id = auth.uid(). 팀 공유(meetings)는 본인 ORteam_members조회 subquery. - 쿼터 집계:
increment_daily_usageRPC는SECURITY DEFINER+service_roleonly. 일반 유저는 읽기만. - Realtime:
transcripts테이블만supabase_realtimepublication에 추가 (회의 중 세그먼트 동기화 용). - Edge Functions placeholder: 인증/쿼터 파이프라인은 완성, 실제 AI API 호출은 주석 처리된 실제 구현 코드 포함. V2-3 (Web MVP) 진행 전까지 유지.
Phase V2-5: Mac 빌드 — 로컬 작업 완료
작성/수정된 파일
apps/desktop/src/main/utils/paths.ts—EXE_SUFFIX상수 도입, sox/sidecar/ffmpeg 경로 플랫폼별 분기apps/desktop/src/main/services/SoundEffectService.ts— Macafplay, Linuxaplay분기 추가 (execFile로 안전하게)apps/desktop/src/main/services/ScreenContextService.ts—_getActiveWindowInfo를 win32/darwin로 분기. macOS는osascript로 frontmost process + 윈도우 타이틀 조회 (Accessibility 권한 필요)apps/desktop/scripts/build-sidecar.py—IS_WINDOWS/IS_MACOS분기,EXE_SUFFIX,--noconsole은 Windows에서만, 출력 경로 플랫폼 통합apps/desktop/scripts/install-sox.sh— Mac/Linux에서 시스템 sox를resources/sox/로 복사. macOS는otool로 dylib 의존성 함께 복사 +install_name_tool로 rpath 변경apps/desktop/electron-builder.yml—mac:타겟 추가 (dmg+zip, arm64+x64),hardenedRuntime,entitlements,extendInfo(권한 메시지), Linux AppImage placeholderapps/desktop/build/entitlements.mac.plist— 마이크/카메라/네트워크/Apple Events/JIT/dylib 권한apps/desktop/resources/icons/README.md— 플랫폼별 아이콘 생성 가이드 (sips/iconutil/imagemagick).github/workflows/build-mac.yml— macos-14 runner, brew sox, sidecar 빌드, electron-builder dist (arm64/x64 매트릭스), CSC/notarize 환경변수, artifact 업로드docs/v2/phase-V2-5-mac-guide.md— Mac 사전 조건/dev 실행/dist 빌드/서명/공증/CI/트러블슈팅
검증 결과 (Windows에서)
- typecheck ✅ — Mac 분기 추가에도 회귀 없음
- build ✅ — Windows 번들 정상
- dev 런타임 ✅ — Ollama 자동 실행 + 모든 서비스 정상
Mac에서 검증 대기 (사용자 액션)
brew install sox node@22 python@3.11npm install && npx @electron/rebuild@3 --version=33.4.11npm run dev→ Accessibility/마이크/Apple Events 권한 허용- 핫키, 녹음, STT, LLM, 화면 컨텍스트 동작 확인
- 결과 리포트 받아 미세 조정 필요할 수 있음
감사 결과 vs 처리
- C1 sox.exe/sidecar.exe/ffmpeg.exe 하드코딩 → ✅ 수정
- C2 아이콘 부재 → ✅ placeholder 디렉토리 + README
- C3 mac 타겟 부재 → ✅ 추가
- C4 SoX 바이너리 부재 → ✅ install-sox.sh + 가이드
- C5 ScreenContextService 미지원 → ✅ AppleScript 분기
- C6 SoundEffectService 미지원 → ✅ afplay 분기
- C7 build-sidecar.py Windows 전제 → ✅ 크로스 플랫폼
- M6/M7/M8/M9 (이미 분기 완료) → 변경 없음
Phase V2-3: Web App MVP — 완료
신규 패키지
packages/api-client/(@d3ro/api-client) — Supabase 래퍼src/types.ts— V2-2 스키마에 맞춘 수동 Database 타입 정의 (profiles/teams/meetings/transcripts/history/dictionary/daily_usage/subscriptions)src/client.ts— createD3roSupabaseClient 팩토리 (URL/KEY 주입형, env 없어도 placeholder로 작동)src/auth.ts— signInWithOAuth/signOut/getSessionsrc/meetings.ts— CRUD 함수 (meetings, memos, documents, transcripts)src/history.ts— 음성 입력 이력 CRUDsrc/usage.ts— daily_usage 조회 (쿼터 UI)- 루트 barrel은 types만 re-export (함수는 subpath로, 순환 의존 방지)
신규 앱 — apps/web/
- Next.js 15 App Router + MUI 7 + Emotion
- transpilePackages로
@d3ro/core/ui/i18n/api-client소스 TypeScript 그대로 번들 - 라우트 (총 9개):
/— auth 상태에 따라 리다이렉트/login— Google/GitHub OAuth (Supabase 미설정 시 경고)/auth/callback— OAuth code → session 교환 route handler/dashboard— 요약 카드 4개 + 최근 회의 5건/meetings— 회의 카드 그리드 리스트/meetings/[id]— 상세 (transcripts + memos + documents 탭)/record— 녹음 페이지 (getUserMedia + MediaRecorder + stt-proxy)
- Providers: ThemeProvider(MUI+AppRouterCacheProvider) / I18nProvider(localStorage 어댑터) / AuthProvider(Supabase session)
- 인증 가드:
/dashboardlayout +/meetings,/record,/meetings/[id]개별 redirect - Sidebar: Dashboard / Meetings / Record / Logout
- Recorder: 9바 웨이브폼 (Speakly 스타일), 실시간 레벨, elapsed timer, cancel/stop
packages/ui 확장 (@d3ro/ui)
MetalCard:BoxProps확장하여sx/onClick등 전달 가능theme.ts에typoSx(key)헬퍼 추가 — d3roTypo를 MUI sx 친화 포맷으로 변환theme.ts에d3roPalette.tag.blue추가 (M5 수정의 부산물)- DS 컴포넌트 9개에
'use client'directive 추가 — RSC 경계 명시 MetalDial.tsx의 미사용 import 3개 제거 (tsc strict 회귀)
packages/i18n 확장
ko.json에 7개 새 키:nav.meetings,nav.record,nav.logout,login.subtitle,login.google,login.github,login.terms- 다른 locale은 fallback 체인으로 ko/en 사용 (추후 번역 작업에서 채움)
설계 결정사항
- Database 제네릭: 현재
SupabaseClient기본 타입 사용, V2-4에서supabase gen types typescript --local자동화 후 제네릭 주입 예정. 이유: 수동 Database 타입 정의가 @supabase/supabase-js 2.102의 복잡한 제약(PostgrestVersion, Views/Functions/Enums 형식)을 만족시키기 어려움. - api-client barrel 분리: 루트에서는 types만 export. 함수는 subpath(
@d3ro/api-client/meetings)로만 접근. 이렇게 하지 않으면 apps/web이 api-client 루트를 import할 때 내부 meetings/history 등이 모두 tsc 스캔 대상이 되어 타입 에러가 전파됨. - DS 컴포넌트
'use client': MUI + React 훅(CrtDisplay)을 쓰는 컴포넌트들은 Next.js RSC에서 쓰려면 client directive 필요. barrel 대신 각 파일마다 붙임. - Supabase env 없이도 빌드 성공: placeholder URL/key로 createBrowserClient/createServerClient를 호출. 런타임에
isSupabaseConfigured()체크로 UI 경고 표시.
검증 결과
- web typecheck ✅ (apps/web)
- desktop typecheck ✅ (회귀 없음)
- web
next build✅ — 9 라우트 (/ /login /auth/callback /dashboard /meetings /meetings/[id] /record /_not-found + root) - desktop
npm run build✅ (main+preload+renderer) - 실제 Supabase 연결은 V2-2 사용자 액션 이후 검증 가능
Phase V2-4: 데스크톱 ↔ Supabase 동기화 — 완료
핵심 컴포넌트
apps/desktop/src/main/services/CloudSyncService.ts— 싱글톤 + EventEmitterinit/startSignIn/handleAuthCallback/signOut/pushAll+ 4종 이벤트- safeStorage 기반 refresh token 영속화
- upsert(onConflict: id) 방식 push, 매핑 테이블 없음
ipc/cloud-sync-handlers.ts+preload index.tscloudSync APIrenderer/components/CloudSyncSection.tsx— Settings 섹션main/index.tsdeep link (d3ro-voice://auth-callback) 핸들러 추가bootstrap.tscloud-sync 단계 추가packages/core/src/ipc-channels.tsCLOUD_SYNC 11종 채널packages/core/types.tsAppConfig + ConfigService defaults에 supabaseUrl/supabaseAnonKey/cloudSyncLastAt + hfToken/diarizationEnabled (Phase 15.5 누락분 보충)
설계 결정
- Local-first push only (V2-4b에서 pull 추가)
- 마지막 동기화 epoch ms 기준 변경분만 전송
- nanoid PK를 PostgreSQL text로 그대로 upsert
- macOS open-url + Windows second-instance argv 방식 deep link
Phase V2-6: Mobile MVP — 완료
구조 (apps/mobile/, npm workspace에서 제외)
- Expo SDK 51 + React Native + Expo Router
lib/supabase.ts(AsyncStorage),lib/auth-context.tsxapp/_layout.tsx,app/index.tsx,app/login.tsxapp/(tabs)/{_layout, meetings, record, profile}.tsxexpo-av로 녹음 → stt-proxy 호출README.md+ setup 가이드
설계 결정
- workspace 제외 (Expo native deps 부담 회피)
- MUI 미사용, 인라인 RN 스타일 (V2-6b에서
@d3ro/ui-native분리) - Deep link
scheme: 'd3ro-voice'
Phase V2-7: 팀 기능 (Web) — 완료
apps/web/src/app/teams/page.tsx— 가입한 팀 카드 그리드apps/web/src/app/teams/[id]/page.tsx— 팀 상세 (멤버 + 공유 회의)components/teams/create-team-form.tsx— 팀 생성 + owner 자동 등록components/teams/invite-member-form.tsx— user_id 직접 입력 (V2-7b에서 정식 invite flow)- Sidebar에 Teams/Billing 메뉴 + ko.json 새 키
- V2-2의 teams/team_members RLS 활용
Phase V2-8: 결제 스캐폴딩 — 완료
apps/web/src/app/billing/page.tsx— Free/Pro/Team 가격표 + 현재 티어components/billing/checkout-button.tsx— Edge Function 호출server/supabase/functions/stripe-checkout/index.ts— JWT 인증, customer 조회/생성, Checkout Sessionserver/supabase/functions/stripe-webhook/index.ts— checkout.session.completed, subscription.* 이벤트 → subscriptions 업데이트config.toml에 두 함수 등록 (webhook verify_jwt=false)
사용자 액션 필요
STRIPE_SECRET_KEY/STRIPE_PRICE_PRO/STRIPE_PRICE_TEAM/STRIPE_WEBHOOK_SECRET설정- Stripe 대시보드에서 product + price 생성
미구현 (V2-8b)
- Webhook signature 정식 검증 (현재 placeholder)
- Customer Portal
- Per-seat billing
알려진 차단/이슈
- @nut-tree-fork/nut-js 포크 사용 (V1 잔여 이슈)
- Ollama 의존성: 없으면 LLM 기능 사용 불가 (2026-04-08 자동 실행 기능 추가로 UX 개선)
디렉토리 구조 (V2-1a 목표)
d3ro-voice/ # 루트 (monorepo)
├── apps/
│ └── desktop/ # 기존 V1 전체
│ ├── src/
│ ├── resources/
│ ├── build/
│ ├── package.json # name: @d3ro/desktop
│ ├── electron.vite.config.ts
│ ├── electron-builder.yml
│ ├── tsconfig*.json
│ └── drizzle.config.ts
├── packages/ # V2-1b/c/d에서 채움
├── site/ # 랜딩 페이지 (루트 유지)
├── docs/ # 설계서/페이즈 문서
├── memory/ # 이 파일
├── pnpm-workspace.yaml # 또는 package.json "workspaces"
├── turbo.json
├── package.json # 루트 workspace
├── tsconfig.base.json
└── CLAUDE.md