Commit graph

12 commits

Author SHA1 Message Date
윤찬
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
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
yunchan8804
1fa24ce3c9 feat(V2-4차): Database 제네릭 복원 + /chat + /knowledge + Resend + Push + 문서 생성
묶음 K — Database 제네릭 완전 주입:
- @supabase/ssr 0.5→0.10, @supabase/supabase-js 2.45→2.103 bump
- 버전 정합으로 @supabase/ssr의 Database 제약이 정상 동작
- packages/api-client/src/types.ts:
  - TypedTable<Row, Insert, Update>로 Row & Record<string, unknown> 교차 유지
  - 각 테이블 Insert를 Partial<Row> & Pick<필수필드>로 교체
    (optional 컬럼이 required로 추론되던 문제 해결)
  - push_tokens / team_invites 테이블 타입도 inline 추가
- index.ts: types + client + auth + meetings + history + usage 전체 re-export 복원
- apps/web supabase-browser/server에 <Database> 제네릭 주입

묶음 L — /chat 페이지 (Voice Conversation 이식):
- apps/web/src/components/chat/chat-panel.tsx (client)
  - 메시지 state, user/assistant 말풍선, MetalCard 래핑
  - llm-proxy POST (non-streaming JSON), Anthropic 응답 형식 파싱
  - Enter 전송, Shift+Enter 줄바꿈, 초기화 버튼
- apps/web/src/app/(app)/chat/page.tsx
- Sidebar에 Chat 메뉴 추가 (ChatIcon)
- ko.json nav.chat 키

묶음 M — /knowledge 페이지 (RAG 이식):
- migrations/20260410000002_knowledge_documents.sql
  - knowledge_documents + knowledge_chunks 테이블
  - tsvector 자동 생성 컬럼 + GIN 인덱스 (전문 검색)
  - RLS: 개인/팀 분기 (team_id NULL 가능)
  - pgvector는 주석 처리 (V2-M+1에서 활성화)
- apps/web/src/app/(app)/knowledge/page.tsx — 카드 리스트 + 상태 배지
- components/knowledge/add-knowledge-form.tsx — 제목/타입/본문 입력,
  800자 단위 청킹 후 documents+chunks INSERT
- api-client types.ts에 KnowledgeDocument/Chunk 추가 + Database 등록
- Sidebar Knowledge 메뉴 (LibraryBooksIcon) + ko.json 키

묶음 N — team-invite Resend 이메일:
- functions/team-invite/index.ts에 Resend API 호출 로직 추가
- RESEND_API_KEY 설정 시 HTML 이메일 발송 (팀 이름/초대자/버튼/만료)
- 응답에 email_sent / email_error 포함
- API 키 없으면 기존대로 URL만 반환

묶음 O — Expo Push notification:
- migrations/20260410000003_push_tokens.sql (user_id, token unique, platform)
- functions/send-push/index.ts: 호출자 인증 + 본인/팀 멤버 권한 체크,
  대상 사용자 push_tokens 조회, Expo Push API 배치 호출
- config.toml에 send-push 함수 등록
- apps/mobile/package.json에 expo-device + expo-notifications 추가
- apps/mobile/lib/push.ts: registerPushToken (권한 요청, projectId,
  Android 채널, Supabase upsert)
- auth-context.tsx에서 로그인 직후 자동 등록
- app.json plugins에 expo-notifications

묶음 P — meetings/[id] 문서 생성:
- apps/web/components/meetings/generate-document-button.tsx (client)
  - 4개 템플릿 메뉴 (minutes/report/idea-note/mindmap)
  - 각 템플릿별 systemPrompt 지정
  - llm-proxy 호출 → meeting_documents INSERT
  - latency 측정, prompt_used 기록
- meetings/[id]/page.tsx DOCUMENTS 섹션에 버튼 노출
  (transcript는 edited_transcript ?? raw_transcript 우선)

검증:
- desktop typecheck + build OK
- web typecheck + build OK (14 라우트: 기존 12 + chat + knowledge)
- api-client test 19 passed

통계:
- 총 Edge Functions 10개: stt/llm-proxy, stripe-checkout/portal/webhook,
  team-invite/accept, send-push
- 총 SQL 마이그레이션 7개
- 웹 라우트 14개, 모바일 화면 5개
- 테스트 19개 passed
2026-04-10 08:51:53 +09:00
yunchan8804
c167737198 feat(V2-3차): pull 확장 + invite flow + Realtime + 테스트 + mobile UI 교체
묶음 E — V2-4b pull 확장:
- CloudSyncService.pullAll()에 meetings/meeting_memos/meeting_documents 추가
- meetings: LWW, 모든 컬럼 매핑 (minutes_json JSON 직렬화)
- meeting_memos: immutable INSERT-only 전략
- meeting_documents: LWW UPDATE

묶음 F — api-client types:
- interface -> type alias 전환 (11개)
- Database 타입에 TypedTable<Row, Insert, Update> 유틸 도입
  (Row & Record<string, unknown> 교차로 GenericTable 제약 만족)
- @supabase/ssr 2.102는 supabase-js 버전 불일치로 제네릭 주입 불가 -
  다음 사이클로 이월, api-client index.ts는 types만 재수출

묶음 G — apps/mobile UI 교체:
- login.tsx: MetalCard + PhosphorText(hero/label) + PhysicalButton
- meetings.tsx: MetalCard + Led(status) + PhosphorText(body/meta)
- record.tsx: Led + PhosphorText + PhysicalButton
- profile.tsx: MetalCard + PhysicalButton(danger) + d3roNativePalette

묶음 H — V2-7b 이메일 invite flow:
- migrations/20260410000001_team_invites.sql
  - team_invites 테이블 (token, email, role, expires_at 7일)
  - RLS: 같은 팀 멤버 + 초대 이메일 소유자 SELECT,
    owner/admin만 INSERT/DELETE
  - generate_invite_token() SECURITY DEFINER RPC (service_role)
- functions/team-invite: 권한 체크 -> 토큰 생성 -> 초대 URL 반환
- functions/team-accept: 토큰 검증 -> expires_at/accepted_at/이메일 일치 ->
  team_members upsert -> 초대 accepted 표시
- config.toml에 team-invite/team-accept 함수 등록
- InviteMemberForm 재작성: 이메일 입력 + 역할 선택 -> URL 복사 UI
- /accept-invite 페이지 신규 (Suspense 내 useSearchParams + token 수락)

묶음 I — Realtime transcripts:
- apps/web/components/meetings/live-transcript-list.tsx (client)
  - supabase.channel('transcripts:meeting:${id}').on('postgres_changes')
  - INSERT -> 세그먼트 추가, UPDATE -> row 교체
  - 중복 방지 segment_index 기준
  - edited 배지 표시
- meetings/[id]/page.tsx의 transcript 섹션을 LiveTranscriptList로 교체

묶음 J — 테스트:
- packages/api-client/__tests__/client.test.ts (9 tests)
  - createD3roSupabaseClient, isClientConfigured 팩토리 검증
- packages/api-client/__tests__/types.test.ts (10 tests)
  - 모든 Row 타입 + 리터럴 union + Database keyof
- vitest.config.ts 신규
- apps/web/playwright.config.ts 신규 (baseURL, webServer dev 서버)
- apps/web/e2e/smoke.spec.ts 신규 (6 스모크 테스트)
- apps/web tsconfig exclude에 e2e/playwright.config.ts 추가
- package.json scripts: test, test:e2e, test:e2e:ui

검증:
- desktop typecheck OK
- web typecheck OK
- web next build OK (12 라우트, /accept-invite Suspense 적용)
- desktop build OK
- api-client test 19 passed
2026-04-10 08:34:52 +09:00
yunchan8804
0785374804 feat(V2-X-b): UI 정리 + pull 동기화 + Stripe 서명 검증 + ui-native 패키지
묶음 A — UI 정리 + i18n 완성:
[A1] desktop SettingsModal에 CloudSyncSection 통합 (탭 6번째)
  - CloudIcon import, settings.tabs.cloud 키 추가
  - 기존 about 탭은 index 5 -> 6

[A2] apps/web Sidebar 공유 layout 리팩터링
  - app/(app)/layout.tsx 신규 (route group)
  - dashboard/meetings/record/teams/billing을 (app)/ 아래로 git mv
  - app/(app)/layout.tsx에 auth 가드 + Sidebar 통합
  - 기존 개별 page.tsx에서 Sidebar/auth 중복 제거
  - app/(app)/dashboard/layout.tsx 제거 (루트 layout이 처리)

[A3] 11개 locale에 V2 새 키 추가 (en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th)
  - nav.meetings/record/teams/billing/logout
  - login.subtitle/google/github/terms
  - settings.tabs.cloud

묶음 B — V2-4b pull 동기화:
- CloudSyncService.pullAll() 신규
  - history/dictionary 테이블 원격에서 fetch
  - last_sync_at 이후 updated_at만 필터
  - Last-Write-Wins 충돌 해결 (remote.updated_at > local.updated_at)
  - 로컬에 없는 행은 INSERT, 있는 행은 UPDATE (구체적 컬럼 지정)
- IPC CLOUD_SYNC.PULL_ALL 채널 + handler + preload api
- CloudSyncSection에 Pull 버튼 추가 (Push 옆에 위치)

묶음 C — V2-8b Stripe webhook 서명 검증:
- stripe-webhook Edge Function에 Web Crypto API 기반 HMAC-SHA256 검증
  - Stripe-Signature 헤더 파싱 (t=, v1= 엔트리)
  - Replay 방지 (timestamp tolerance 300초)
  - constantTimeEqual로 타이밍 공격 방지
  - crypto.subtle.importKey/sign으로 HMAC 계산
- stripe-portal Edge Function 신규 (Customer Portal)
  - JWT 인증 -> 기존 customer_id 조회 -> billing_portal/sessions 생성
  - return_url 지원
- apps/web/components/billing/portal-button.tsx (구독 관리 버튼)
- billing 페이지에 Free 외 tier 사용자에게 PortalButton 표시
- config.toml에 stripe-portal 함수 등록 (verify_jwt=true)

묶음 D — V2-6b packages/ui-native:
- @d3ro/ui-native 신규 패키지 (React Native 전용 DS)
  - theme.ts: d3roNativePalette/Typo/Radius (MUI 없는 정적 값)
  - components/MetalCard.tsx: View + 섀시 섀도우
  - components/PhosphorText.tsx: Text + 앰버 glow (textShadow)
  - components/Led.tsx: View 원 + glow
  - components/PhysicalButton.tsx: Pressable + 누름 느낌
- React/React-Native는 peerDependencies
- apps/mobile/package.json에 @d3ro/ui-native를 file: 의존성으로 추가
  (mobile은 npm workspace 제외이므로 file path 필요)

검증:
- desktop typecheck OK
- web typecheck OK
- web next build OK (11 라우트, (app) 그룹 반영)
- 회귀 없음
2026-04-09 21:36:38 +09:00
yunchan8804
61a96b3e9b feat(V2-6/V2-7/V2-8): Mobile MVP + Teams + Billing 스캐폴딩
V2-6 — Mobile (Expo) MVP
- apps/mobile/ 신규, npm workspace에서 제외 (Expo deps 부담 회피)
- Expo SDK 51 + Expo Router + AsyncStorage Supabase 클라이언트
- 화면: index/login/(tabs)/{meetings,record,profile}
- expo-av로 녹음 → Edge Function stt-proxy 호출
- expo-web-browser + expo-linking으로 OAuth 콜백 처리
- README에 setup/EAS build 가이드
- 루트 package.json workspaces를 명시 나열로 변경 (apps/mobile 제외)

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 자동 team_members
- components/teams/invite-member-form.tsx — user_id 직접 초대 (V2-7b에서 invite flow)
- Sidebar에 Teams/Billing 메뉴 + 아이콘
- ko.json에 nav.teams/nav.billing 키 추가
- V2-2 teams/team_members RLS 활용

V2-8 — 결제 스캐폴딩
- apps/web/src/app/billing/page.tsx — Free/Pro/Team 가격표 + 현재 구독
- components/billing/checkout-button.tsx — Edge Function 호출 후 redirect
- server/supabase/functions/stripe-checkout/index.ts:
  - JWT 인증 -> 기존 customer 조회/생성 -> Checkout Session 생성
  - subscriptions 테이블에 customer_id upsert
- server/supabase/functions/stripe-webhook/index.ts:
  - checkout.session.completed -> subscriptions tier=pro/team active
  - customer.subscription.updated/created -> status/period 업데이트
  - customer.subscription.deleted -> tier=free, status=canceled
  - signature 검증은 placeholder (V2-8b에서 정식)
- server/supabase/config.toml에 두 함수 등록 (webhook verify_jwt=false)

검증:
- desktop typecheck OK (회귀 없음)
- web typecheck OK
- web next build OK (11 라우트)
- mobile은 별도 install 필요 (workspace 제외)

memory/project_status.md 갱신 — V2 마스터 플랜 전 페이즈 로컬 완료
2026-04-09 16:39:54 +09:00
yunchan8804
5c0f4a2b98 feat(V2-4): 데스크톱 ↔ Supabase 동기화 (push only MVP)
CloudSyncService (싱글톤 + EventEmitter):
- Supabase 클라이언트 lazy init (configGet으로 url/key)
- safeStorage 기반 refresh token 영속화 (cloud-sync.token)
- startSignIn(provider): OAuth URL 발급 후 shell.openExternal로 외부 브라우저
- handleAuthCallback(code): code -> session 교환
- pushAll(): history/dictionary/meetings/meeting_memos/meeting_documents
  를 last_sync_at 이후 변경분만 upsert (onConflict: id)
- 이벤트: auth-changed / sync-progress / sync-complete / sync-error

Deep link (d3ro-voice://auth-callback):
- main/index.ts에 setAsDefaultProtocolClient
- macOS open-url 핸들러
- Windows second-instance argv 검사
- handleDeepLink로 URL 파싱 후 CloudSyncService로 전달

IPC:
- @d3ro/core/ipc-channels에 CLOUD_SYNC 채널 추가
- ipc/cloud-sync-handlers.ts (configure/signIn/signOut/pushAll/state)
- preload index.ts에 cloudSync 노출
- bootstrap의 cloud-sync 단계 추가

Renderer:
- CloudSyncSection 컴포넌트 신규
  - 미설정: Supabase URL/Key 입력 폼
  - 미로그인: Google/GitHub OAuth 버튼
  - 로그인: 사용자 이메일 + Sync Now + 진행 바 + 로그아웃
- d3roPalette/typoSx로 SSOT 준수, useI18n 사용

ConfigService 키 5종 추가:
- hfToken, diarizationEnabled (Phase 15.5 누락분 보충)
- supabaseUrl, supabaseAnonKey, cloudSyncLastAt (V2-4)
- packages/core types.ts AppConfig에도 동일하게 추가

검증:
- desktop typecheck OK
- desktop build OK
- 실제 Supabase 동기화는 사용자가 V2-2 배포 + 설정 입력 후 검증
2026-04-09 16:28:19 +09:00
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
yunchan8804
9742b2109a fix(V2-1): minor 이슈 정리 [M4][M5]
[M5] EditableSegment.tsx의 '#3b82f6' 하드코딩 제거:
- packages/ui theme.ts의 tag 팔레트에 blue/blueBg 추가
- SPEAKER_COLORS 배열이 d3roPalette.tag.blue 참조

[M4] 루트 .eslintignore 추가:
- site/, server/supabase/functions/ 등 자체 lint 설정이 있는
  서브 프로젝트 제외
- node_modules/out/dist/.next/release/sidecar-dist 제외
- apps/desktop/build/entitlements.mac.plist는 예외로 포함
2026-04-09 02:32:37 +09:00
yunchan8804
3524e958ba feat(V2-1d): packages/i18n 추출 — Electron 독립 i18n 엔진
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 독립성 확보 (monorepo 원칙 준수):
- I18nStorage interface 신설 (load/save 어댑터 추상화)
- window.electronAPI.config 직접 호출 제거
- I18nProvider는 storage prop으로 영속화 어댑터 주입
- 기본값 noopStorage (메모리 한정, 세션 범위)
- 결과: packages/i18n은 React에만 의존, web/mobile에서 재사용 가능

apps/desktop 어댑터:
- App.tsx에 electronI18nStorage 구현 (config.get/set 래핑)
- <I18nProvider storage={electronI18nStorage}>로 주입

apps/desktop 설정:
- package.json: @d3ro/i18n '*' dep 추가
- tsconfig.node/web.json paths에 @d3ro/i18n 추가
- electron.vite.config.ts alias + externalize exclude 추가
- vitest.config.ts alias 추가

일괄 치환 (29 파일):
- ./i18n, ../i18n, ../../i18n → @d3ro/i18n

apps/desktop/src/renderer/i18n/ 디렉토리 완전 제거.

검증: typecheck + build + dev 런타임 모두 통과.
Phase V2-1 전체 완료 (a/b/c/d).
2026-04-08 14:57:27 +09:00
yunchan8804
a041f1b6a9 feat(V2-1c): packages/ui 추출 — DS 컴포넌트 + theme + theme-vars 분리
packages/ui (@d3ro/ui) 신규:
- src/theme.ts (d3roPalette/d3roTypo/d3roShadow/d3roRadius SSOT)
- src/theme-vars.ts (팝업/main 프로세스용 CSS 변수 맵)
- src/components/ds/ (CrtDisplay, InstrumentPanel, Led, MetalCard,
  MetalDial, PhosphorText, PhysicalButton, ScreenPanel, ButtonGroup)
- src/index.ts barrel
- subpath exports: ./theme, ./theme-vars, ./components/ds
- React/MUI/Emotion은 peerDependencies로 선언
- @d3ro/core만 직접 의존성

apps/desktop/src/shared/ 디렉토리 완전 제거:
- theme-vars가 마지막 남은 파일이었음
- tsconfig include에서 src/shared/**/* 제거

일괄 치환 (renderer 전역):
- ../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)

apps/desktop 설정:
- package.json: @d3ro/ui: '*' dep 추가
- tsconfig.node/web.json: @shared/* paths 완전 제거, @d3ro/ui,
  @d3ro/ui/* paths 추가
- electron.vite.config.ts: @shared alias 제거, @d3ro/ui alias 추가,
  externalize exclude에 @d3ro/ui 추가
- vitest.config.ts: alias 교체

DS 컴포넌트 내부의 '../../theme' 상대 경로는 packages/ui 구조에서
동일하게 해결되어 그대로 유효.

검증: typecheck + build + dev 런타임 모두 통과.
2026-04-08 14:50:33 +09:00
yunchan8804
3b0eb3393b feat(V2-1b): packages/core 추출 — 공유 타입/에러/채널/유틸 분리
packages/core (@d3ro/core) 신규 생성:
- types.ts, errors.ts, ipc-channels.ts, constants.ts (shared에서 이동)
- utils/meeting-markdown.ts, utils/markdown-to-docx.ts (main/utils에서 이동)
- subpath exports 정의 (./types, ./errors, ./ipc-channels, ./constants,
  ./utils/meeting-markdown, ./utils/markdown-to-docx)
- src/index.ts barrel export 추가
- docx를 core 자체 dependency로 선언

apps/desktop 연결:
- package.json에 @d3ro/core: '*' dep 추가
- tsconfig.node/web.json paths에 @d3ro/core/* 추가
- electron.vite.config.ts 3개 섹션 alias 추가 (main/preload/renderer)
- externalizeDepsPlugin exclude에 @d3ro/core (workspace 소스 번들 대상)
- vitest.config.ts alias 추가

일괄 치환 (79 파일, 167건):
- @shared/{types,errors,ipc-channels,constants} → @d3ro/core/*
- static/dynamic import + type expression import 모두 포함
- MeetingModeService.ts의 ../utils/* 상대 경로 → @d3ro/core/utils/*
- @shared/theme-vars는 V2-1c 범위로 남김 (WindowManager만 사용)

M1 수정 포함:
- electron.vite.config.ts의 resolve('src/shared') → resolve(__dirname, ...)
  CWD 독립적으로 동작하도록 견고화

검증:
- typecheck 통과
- build 통과 (main+preload+renderer)
- dev 런타임 → DB/핫키/Ollama 모두 정상, 기존 데이터 연속성 유지
2026-04-08 14:39:46 +09:00