d3ro-voice/docs/v2/phase-V2-3.md
yunchan8804 d0c33ca259 feat(V2-3): Web App MVP — Next.js 15 + @d3ro/api-client
packages/api-client (@d3ro/api-client) 신규:
- types.ts: 12개 테이블 Row/Insert/Update 타입 + Database 형식
- client.ts: createD3roSupabaseClient 팩토리 (placeholder fallback)
- auth.ts: signInWithOAuth/signOut/getSession/onAuthStateChange
- meetings.ts: 회의 + 메모 + 문서 + 전사 CRUD
- history.ts: 음성 입력 이력 CRUD
- usage.ts: 일일 쿼터 + 구독 조회
- 루트 barrel은 types만 re-export, 함수는 subpath import 전용

apps/web (@d3ro/web) 신규 — Next.js 15 App Router:
- transpilePackages: @d3ro/core, ui, i18n, api-client
- ThemeProvider (MUI + AppRouterCacheProvider)
- I18nProvider (localStorage 어댑터)
- AuthProvider (Supabase session Context)
- 라우트 9개:
  - / (auth 상태 기반 리다이렉트)
  - /login (Google/GitHub OAuth, 미설정 경고)
  - /auth/callback (code -> session 교환)
  - /dashboard (요약 카드 + 최근 회의)
  - /meetings (카드 그리드 리스트)
  - /meetings/[id] (transcripts/memos/documents)
  - /record (getUserMedia + MediaRecorder + stt-proxy)
- Sidebar, 인증 가드, 9바 웨이브폼, 레벨 미터

packages/ui 확장:
- MetalCard가 BoxProps 상속 (sx/onClick 등 전달)
- theme.ts에 typoSx(key) 헬퍼 추가 (d3roTypo -> MUI sx 변환)
- d3roPalette.tag.blue 추가 (M5 정리 포함)
- DS 컴포넌트 9개에 'use client' directive
- MetalDial 미사용 import 제거

packages/i18n 확장:
- ko.json에 7개 새 키 (nav.meetings/record/logout, login.*)

설계 결정:
- Database 제네릭 현재는 기본 타입 (V2-4에서 supabase gen types로 자동화)
- api-client barrel은 타입만 노출해서 컴파일 전파 차단
- DS 컴포넌트 client 경계 명시
- env 없어도 Next.js 빌드 성공 (placeholder URL/key)

검증:
- web typecheck OK
- desktop typecheck OK (회귀 없음)
- web next build OK (9 라우트 정적/동적 생성)
- desktop build OK (회귀 없음)
2026-04-09 02:52:31 +09:00

8.4 KiB

Phase V2-3: Web App MVP — 상세 설계

브라우저에서 로그인 → 녹음 → 전사 → 회의록 생성 → 편집을 수행하는 Next.js 앱. V2-1의 packages/{core,ui,i18n}과 V2-2의 Supabase 인프라를 소비하는 첫 consumer.


1. 목표

  1. 로그인 가능한 웹앱: Supabase Auth OAuth (Google/GitHub)
  2. 녹음 → 전사 파이프라인: 브라우저 마이크 → Edge Function(stt-proxy) → DB 저장
  3. 회의록 CRUD: 리스트, 상세, 편집, 삭제
  4. AI 문서 생성: 회의록 → llm-proxy 호출 → meeting_documents 저장
  5. UI 재사용: @d3ro/ui DS 컴포넌트를 Next.js에서 사용
  6. Supabase 미설정 상태에서도 빌드 성공: env 없어도 next build 통과 (런타임 placeholder)

2. 디렉토리 구조

apps/web/
├── package.json                 # @d3ro/web, Next.js 15, MUI 7, @d3ro/core/ui/i18n/api-client
├── next.config.mjs              # transpilePackages: ['@d3ro/ui', '@d3ro/core', '@d3ro/i18n', '@d3ro/api-client']
├── tsconfig.json                # extends ../../tsconfig.base.json
├── next-env.d.ts
├── .env.local.example           # NEXT_PUBLIC_SUPABASE_URL / ANON_KEY 템플릿
├── public/
└── src/
    ├── app/
    │   ├── layout.tsx           # 루트 layout + ThemeProvider + I18nProvider
    │   ├── page.tsx             # / — 랜딩/로그인 리다이렉트
    │   ├── login/
    │   │   └── page.tsx         # /login — OAuth 버튼
    │   ├── auth/
    │   │   └── callback/
    │   │       └── route.ts     # OAuth 콜백 처리
    │   ├── dashboard/
    │   │   ├── layout.tsx       # 인증 가드 + 사이드바
    │   │   └── page.tsx         # /dashboard — 요약 카드
    │   ├── meetings/
    │   │   ├── page.tsx         # /meetings — 리스트
    │   │   └── [id]/
    │   │       └── page.tsx     # /meetings/[id] — 상세
    │   └── record/
    │       └── page.tsx         # /record — 녹음 페이지
    ├── components/
    │   ├── providers/
    │   │   ├── theme-provider.tsx   # MUI + @d3ro/ui theme
    │   │   ├── i18n-provider.tsx    # @d3ro/i18n + localStorage 어댑터
    │   │   └── auth-provider.tsx    # Supabase session context
    │   ├── layout/
    │   │   ├── sidebar.tsx
    │   │   └── top-bar.tsx
    │   └── record/
    │       ├── mic-recorder.tsx     # getUserMedia + MediaRecorder
    │       └── waveform-bars.tsx    # 웨이브 애니메이션 (DS 스타일)
    └── lib/
        ├── supabase-server.ts        # RSC/route handler용 서버 클라이언트
        ├── supabase-browser.ts       # 클라이언트 컴포넌트용
        └── route-guards.ts            # requireUser() 헬퍼

3. packages/api-client 설계

별도 패키지로 만들어 web과 desktop이 공유.

packages/api-client/
├── package.json              # @d3ro/api-client
├── tsconfig.json
└── src/
    ├── index.ts              # barrel
    ├── client.ts             # createSupabaseClient (url + key 주입형 팩토리)
    ├── types.ts              # Database row 타입 (supabase gen types 대체)
    ├── auth.ts               # signInWithOAuth, signOut, getSession
    ├── meetings.ts           # CRUD 함수 (list, get, create, update, delete)
    ├── history.ts            # history 테이블 CRUD
    └── usage.ts              # daily_usage 조회 (쿼터 표시용)

주요 설계 포인트

  • 환경변수 주입형: createClient(url, anonKey)를 factory로 제공. 각 앱(web/desktop)이 자체 env를 읽어 주입.
  • 타입: V2-2의 SQL 스키마와 동기화. Supabase CLI로 supabase gen types typescript 실행 가능하지만 V2-3에서는 수동 타입 정의로 시작 (추후 자동화).
  • SSR/CSR 양립: Next.js RSC에서도 쓸 수 있도록 createClient는 SupabaseClient를 반환만 하고 상태를 들고 있지 않음.

4. Next.js 환경 설정 고려사항

4.1 transpilePackages

Monorepo의 .ts 소스를 Next.js가 직접 해석하도록:

// next.config.mjs
export default {
  transpilePackages: ['@d3ro/core', '@d3ro/ui', '@d3ro/i18n', '@d3ro/api-client']
}

4.2 MUI + Emotion RSC 호환

Next.js 15 App Router는 기본 RSC. MUI 컴포넌트는 'use client' 필요. → 루트 layout은 서버 컴포넌트 유지, <ThemeProvider>를 client component로 래핑. → @mui/material-nextjs/v15-appRouterAppRouterCacheProvider 사용.

4.3 @d3ro/ui 컴포넌트 소비

DS 컴포넌트는 MUI 기반이라 그대로 사용 가능. 단, 컴포넌트마다 'use client'가 필요한지 확인. → packages/ui/src/components/ds/*.tsx는 MUI Box/Typography 사용 → client 필요 → 해결: 각 컴포넌트 상단에 'use client' 추가하거나, Next.js 측에서 client wrapper 도입. → MVP는 page.tsx에 'use client' 지정하는 방식으로 단순 처리.

4.4 i18n

@d3ro/i18n은 React Context 기반이라 client 전용. I18nProvider는 localStorage를 storage 어댑터로 주입.

5. 인증 흐름 (OAuth)

/login (버튼 클릭)
  → supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: '/auth/callback' } })
  → Supabase가 Google OAuth로 리다이렉트
  → 사용자 동의 후 → /auth/callback?code=...
  → route handler가 code를 session으로 교환
  → Supabase가 쿠키에 세션 저장
  → /dashboard로 리다이렉트

세션 관리

  • 브라우저: @supabase/ssr 패키지의 createBrowserClient (쿠키 기반)
  • RSC/route handler: createServerClient + Next.js cookies()

6. 페이지별 요구사항

/login

  • 중앙 카드: "D3RO Voice" 로고 + OAuth 버튼 2개 (Google / GitHub)
  • 이메일 로그인은 V2-3에서는 제외 (이메일 확인 플로우 복잡, OAuth만)
  • 이미 로그인이면 /dashboard로 리다이렉트

/dashboard

  • 사이드바: Dashboard / Meetings / Record / Settings / Logout
  • 상단 카드 4개: 이번 주 회의 수, 총 녹음 시간, 사용 쿼터, 구독 티어
  • 하단: 최근 회의 5건

/meetings

  • 카드 그리드 (반응형)
  • 각 카드: 제목, 날짜, 상태, duration, 첫 메모 1개
  • 우상단: "새 회의" 버튼 → /record
  • 검색/필터 (MVP에서는 생략)

/meetings/[id]

  • 탭: Transcript / Memos / Documents
  • Transcript: 세그먼트 리스트, 시간 스탬프, 화자 라벨
  • Memos: 메모 리스트
  • Documents: 생성된 문서 목록 + "새 문서 생성" 버튼 (llm-proxy 호출)

/record

  • MicRecorder 컴포넌트
    • 상태: idle | recording | processing | done
    • 버튼: Start / Stop / Cancel
    • 실시간 waveform (9개 바, Speakly 스타일)
  • 녹음 완료 시:
    • Blob → Edge Function stt-proxy로 POST
    • 응답의 transcript 표시
    • "회의로 저장" 버튼 → meetings INSERT

7. Supabase 미설정 시 동작

빌드/dev가 항상 성공해야 함. env 없으면:

  • createSupabaseClient가 dummy client 반환 (모든 쿼리 에러 throw 대신 빈 배열)
  • /login 페이지에 "Supabase가 설정되지 않았습니다" 경고 배너
  • 사용자가 .env.local에 env 추가 후 pnpm dev 재시작

8. 빌드/실행

# 루트에서
npm install   # apps/web 새 workspace 인식

# web dev 실행
npm run dev --workspace=@d3ro/web   # http://localhost:3000

# web 빌드
npm run build --workspace=@d3ro/web

9. 검증 전략

V2-3는 빌드 통과 + RSC/client 경계 정리가 MVP 목표. 실제 Supabase 연결 + 녹음 동작 검증은 사용자가 V2-2 배포 후 수행.

  • typecheck — workspace 전체
  • build — apps/desktop (회귀 없음) + apps/web (Next.js 빌드)
  • apps/web은 Supabase env 없이도 빌드 성공해야 함

10. 결제/Stripe

마스터 플랜에는 포함되지만 V2-3 범위 로 미룸. → Stripe 연동은 V2-8 (결제 + 출시)에서 일괄 구현.

11. 다음 페이즈와의 관계

  • V2-4 (데스크톱 동기화): 이번에 만든 @d3ro/api-client를 apps/desktop에서도 재사용
  • V2-6 (Mobile): Expo 앱도 같은 @d3ro/api-client 사용
  • V2-7 (팀 기능): web의 /teams 페이지 추가
  • V2-8 (결제): /billing 페이지 + Stripe webhook 핸들러 (Supabase Edge Function)