- 앱 아이콘: icon.svg 마스터(d3ro 브랜드 — 메탈 섀시+오렌지 웨이브) → png/ico
생성 스크립트(generate-icons.mjs, sharp) + electron-builder win/mac 연결
- 트레이: createEmpty() 빈 아이콘 → 실제 앱 아이콘 (getAppIconPath)
- 마이크 테스트: 초기 무음 level:0을 종료로 오인하던 조기 종료 버그 —
AUDIO.TEST_LEVEL에 done 플래그 신설(SSOT), STOP 버튼이 실제 캡처 중지,
testDevice 실패 시 상태 롤백
- verifying/manifest 상태(바이트 진행률 없음)에서 인디터미넌트 바 + 친화적 문구
— 9.6GB 검증 ~2분간 멈춘 것처럼 보이던 문제 해소
- OnboardingModal 중복 실행 가드 (runningRef)
- LocalLLMService.pullModel: 동일 모델 동시 pull은 기존 promise 합류
- LocalSTTService.downloadModel: /download 409는 실패가 아닌 기존 진행 합류
RN 0.85 + React 19 기반 apps/mobile-rn 프로젝트 생성.
6개 화면 이식 (Login, Dash, History, Record, Talk, Settings).
@react-navigation/native + bottom-tabs 네비게이션 구성.
Windows NDK CMake libc++_shared 링크 버그 워크아라운드 포함.
Galaxy Fold (SM_F956N) 무선 디버깅 APK 설치 확인.
DS 컴포넌트 9개 (ScreenPanel, WaveBars, AppStatusBar, Header, FilterChip 신규),
5개 탭 + 로그인 화면 D3RO 인스트루먼트 미학 적용,
i18n 연결 (I18nProvider + 모바일 전용 키 80+개),
모노레포 workspaces에 apps/mobile 추가.
- OPEN_BILLING: app.isPackaged 기반 dev/prod URL 분기
- CloudSyncService: stored token 없을 때 auth-changed null emit 누락 수정
- Payple Edge Functions 3개 배포 ACTIVE 확인
## U1 — Realtime TIMED_OUT 자동 재구독
CloudSyncService.startRealtime()의 subscribe() 콜백이 TIMED_OUT 한 번만
로깅하고 끝나던 것을 지수 백오프 재구독으로 교체.
- SUBSCRIBED: retry count reset + timer clear
- TIMED_OUT / CHANNEL_ERROR / CLOSED: _scheduleRealtimeRetry() 트리거
- 백오프: 1s → 3s → 10s (MAX 3회)
- 초과 시 warn 로깅 후 포기 (Auto push fallback으로만 동작)
- stopRealtime()에서 타이머/카운터 cleanup
Publication, RLS, setAuth 모두 설정 완료 상태에서도 매 기동 시 TIMED_OUT이
찍히는 증상. 근본 원인은 Supabase 서버 transient(네트워크/WebSocket
타임아웃) 의심. 이 패치로 완전 해결은 아니지만 일시적 네트워크 jitter
복구 윈도우를 확보하고, 3회 실패 시 명확한 포기 메시지를 남긴다.
## U3 — Refresh token 실패 시 재인증 이벤트
init()의 refreshSession 실패 브랜치에서 warn 로깅 + 토큰 삭제만 수행해
사용자가 "왜 로그아웃됐지?" 상태로 방치되던 문제.
- logger.warn → logger.error(에러 레벨 승격)
- sync-error 이벤트 emit (사용자에게 보여줄 메시지 포함)
- auth-changed { user: null } emit (renderer 게이트 재잠금)
- catch 브랜치도 동일 처리 (기존엔 로깅만)
## U4 — package-lock.json optional dep (유지 결정)
rollup 전 플랫폼 optional deps가 lock에 기록돼 있지만 Windows 빌드자
사전 fetch 효율 + 크로스플랫폼 CI 환경을 고려해 유지. apps/desktop/
package.json:56에 @rollup/rollup-win32-x64-msvc 명시 의존이 있는 이유도
동일. 이 이슈는 확인 후 close.
listening 상태에서 풀 몰입 계측기 모드로 전환되는 VoiceRecordingPanel 추가.
recording-tip 팝업의 9바 cos-분포 waveform(BAR_COUNT=9, SMOOTHING=0.5,
RANDOM_FACTOR=0.35, 100ms)을 React로 포팅해 REC LED + elapsed 타이머 +
"SPEAK NOW" 힌트까지 구성. thinking/speaking 상태에서는 메시지 리스트로
복귀해 대화 맥락 유지 + 점 3개 typing indicator 버블 추가.
VoiceConversationService에 AudioCaptureService audio-level forwarding과
사운드 훅 4개(recording-start / recording-stop / chime / error)를 삽입.
chime은 recording-stop.wav 재사용(SoundEffectService SoundName 확장).
VOICE_CONVERSATION.AUDIO_LEVEL 채널 신설 + preload onAudioLevel API.
U8 Bug 13 동반 해소: finishListening에서 minBytes 미달 또는 VAD 무음 판정으로
빈 텍스트가 나오는 경우 조용히 listening으로 복귀하던 것을 _emitError('stt')로
사용자 피드백(에러 사운드 + 에러 이벤트)을 노출하도록 수정. 사용자가 "⏹ 눌러도
반응 없음"으로 오해하던 증상 해소.
이번 세션 범위:
- Part 5-A: /simplify 패스 — isImeComposingEvent helper 추출, 8곳 IME 가드 통합 (커밋 3b77be0)
- Part 5-B: Voice Conversation 파이프라인 복구 — Bug 10.5/11/12 3개 버그 연쇄 fix (커밋 ee6ac3d)
- Part 5-C: Voice Conversation UX 개선 인터뷰 설계 — 몰입 패널 + 사운드 피드백 + 상태 분기
(ASCII 와이어프레임 + 구현 작업 리스트 handoff에 저장, 다음 세션 이월)
- Part 5-D: Bug 13 신규 발견 — 빈 STT 결과 시 피드백 부재 (U6 몰입 패널과 함께 처리 예정)
갱신 파일:
- memory/project_status.md — SaaS [13] 엔트리 (Bug 10.5/11/12/13, UX 설계 스펙)
- memory/handoff-latest.md — Part 5 전체 요약 + Part 5-C 와이어프레임 + 다음 세션 프롬프트
- memory/handoff-archive/2026-04-11-2200-phase5-part5a-simplify.md — 직전 Part 5-A 스냅샷 아카이브
다음 세션 우선 작업:
1. Voice Conversation 몰입 패널 구현 (handoff의 Part 5-C 작업 리스트 8번까지)
2. Bug 13 피드백 훅 추가 (몰입 패널과 함께)
3. Phase 3.2 PREMIUM_LLM (선행: Anthropic/OpenAI API key 주입)
빅뱅 성공 기준: 8개 중 7개 완료 (Phase 3.2 PREMIUM_LLM만 잔존).
Bug 10 fix(MeetingModePage 인라인 가드)를 isImeComposingEvent helper로 추출하고,
한글 위험도 있는 나머지 7개 Enter 핸들러에 일괄 적용. 총 8곳이 이제 동일 helper 경유.
신규:
- apps/desktop/src/renderer/utils/keyboard.ts — isImeComposingEvent(e)
JSDoc에 Bug 10 원리(Chromium이 IME 조합 중 Enter를 2번 발화) + 권장 사용 패턴 포함
적용 8곳:
- pages/MeetingModePage.tsx:164 회의 메모 (기존 인라인 가드 4줄 교체)
- pages/KnowledgeBasePage.tsx:84 RAG 쿼리
- pages/VoiceConversationPage.tsx:113 텍스트 채팅
- pages/CommandsPage.tsx:336 키워드 추가 (Enter+Esc)
- components/meeting/MeetingChatPanel.tsx:111 미팅 챗
- components/meeting/EditableSegment.tsx:70 전사 세그먼트 편집 (Enter+Esc)
- components/meeting/MeetingDetailTabs.tsx:242 미팅 타이틀 (인라인 arrow → 블록)
- components/shared/HistoryEntryCard.tsx:64 태그 추가 (Enter+Esc)
/simplify 패스 품질 리뷰:
- Phase 3.3 CloudSyncService.pushOne 훅 8곳은 이미 fire-and-forget 1줄로 일관.
내부 try-catch가 에러 삼켜 로컬 write 차단 금지 철학 준수 → 수정 없음, 현 상태가 최적.
검증:
- desktop tsc --noEmit EXIT=0
- Vite HMR로 dev 프로세스 자동 반영 (재기동 없음)
- 한글 Enter 시연은 사용자 실측 대기 (VoiceConversationPage / MeetingDetailTabs 대표 2곳)
Phase 5 Part 3에서 남겨둔 U2(Fix 1 직접 시연 미수행)를 해소하고,
실증 중 발견한 한글 IME Enter 중복 addMemo 버그(Bug 10)를 같이 픽스.
## Bug 10 — 한글 IME Enter 중복 addMemo
- 증상: 실증 녹음 중 한글 메모 "메모 기능이 잘 작동하는지 봅니다"가
2ms 간격으로 2번 저장 (로그 21:47:57.410/.412, UUID c7f0db45 + a6e442cc).
5개 메모 중 이 하나만 중복 — 조합이 아직 확정 안 된 상태에서 Enter를
친 메모에서만 발생.
- 원인: 한글 IME 조합 중 Enter → Chromium이 keydown 2번 발화
((1) IME 조합 확정 isComposing=true/keyCode=229,
(2) 실제 Enter 액션). MeetingModePage.tsx:164-174의 handleMemoKeyDown이
e.key === 'Enter' && !e.shiftKey만 검사하고 isComposing을 무시해
두 이벤트에서 모두 handleAddMemo() 발화 → IPC 2회 → addMemo() 2회.
- 수정: MeetingModePage.tsx:168에 이중 가드 추가.
isComposing은 모던 표준, keyCode === 229는 조합 확정 시점에
isComposing이 false로 내려가면서 keyCode만 229로 주는 Chromium
엣지 케이스 호환.
- 검증: Vite HMR로 즉시 반영 후 재시연 녹음(78421e75)에서 한글 메모
5건 연속 → 전원 1:1 매핑, 중복 0.
## Fix 1 (Phase 3.3 startRecording pre-push) 직접 실증
재시연 녹음 78421e75 (21:51:24~21:51:50, 약 26초) 로그 타임라인:
21:51:24.937 pushOne meetings/78421e75 ok <- Fix 1 pre-push (676ms)
21:51:30.499 pushOne meeting_memos/4ed7e59a ok <- addMemo #1 (113ms)
21:51:32.202 pushOne meeting_memos/de011c15 ok <- addMemo #2 (78ms)
21:51:35.893 pushOne meeting_memos/3184fe5e ok <- addMemo #3 (66ms)
21:51:38.263 pushOne meeting_memos/00f041ef ok <- addMemo #4 (82ms)
21:51:40.740 pushOne meeting_memos/0808c4d2 ok <- addMemo #5 (64ms)
21:51:50.681 pushOne meetings/78421e75 ok <- _runPostProcessing (108ms)
21:51:50.709 pushOne history/70388090 ok <- CaptionService→History
meetings/78421e75가 녹음 시작 + 종료 두 번 push된 것이
Fix 1의 직접 증거 — 부모 row가 메모 push 시점에 이미 Supabase에
존재했기 때문에 메모 5건이 RLS/FK 차단 없이 통과. Bug 7 재현 방지 확인.
## 잠재 IME 버그 7곳
동일 패턴의 Enter 핸들러 7곳에 가드 없음 (KnowledgeBasePage:85,
VoiceConversationPage:114, MeetingChatPanel:113, EditableSegment:72,
MeetingDetailTabs:242, HistoryEntryCard:65, CommandsPage:337).
다음 세션 /simplify 패스에서 일괄 처리 예정.
## 검증
- desktop tsc --noEmit EXIT=0
- Meeting 녹음 2회차 실증 통과
memory/project_status.md SaaS [11] 섹션 추가.
SaaS [9] 갱신 + handoff-latest 재작성.
## 이 세션 성과
- Bug 4/5/6 구조 픽스 (nanoid→UUID, realtime publication, setAuth 명시)
- 실증 A/B/C/D/E 전원 통과 — 3 클라이언트 데이터 일원화 end-to-end 검증 완료
- 로컬→클라우드 push 최초 성공 (실증 B pushed=1, 실증 E pushed=5)
- 웹 Dashboard에 데스크톱 미팅 row 반영 스크린샷 캡처 (RECENT MEETINGS "(제목 없음) 2026.4.11 오후 7:09:21")
- 빅뱅 성공 기준 8개 중 7개 완료 (PREMIUM_LLM만 미구현)
## 미해결 (다음 세션 이월)
- Phase 3.3 Auto push on write — 수동 Push 버튼 대신 세션 완료 즉시 자동 push
- U1: Realtime 채널 TIMED_OUT 근본 원인 (블로커 아님, Bug 5+6 적용 후에도 재현)
- U2: 웹 Dashboard Recent Meetings 카드 클릭 → 상세 이동 안 됨 (사용자 피드백, 해석 확인 필요)
- U3: package-lock.json 정리 지연 (rollup-win32 optional dep로 npm install Mac 실패)
다음 세션 시작 프롬프트는 memory/handoff-latest.md 말미에 그대로 붙여넣기 가능.
아카이브: 이전 handoff는 memory/handoff-archive/2026-04-11-1800-phase5-part1-wrap.md로 이동.
SaaS [9] — 실증 A/B/C/D 전부 통과, 로컬→클라우드 push 최초 성공(pushed=1).
## Bug 4: 로컬 nanoid PK vs Supabase UUID PK 불일치
- 증상: Push history failed: invalid input syntax for type uuid: "fvy6bIzr..."
- 원인: 로컬 drizzle schema는 text PK + nanoid() 생성, Supabase는 uuid PK.
빅뱅 사이클 내내 push가 한 번도 성공한 적 없었음 (지난 pushed=0은 데이터 0건이라서).
- 픽스: 로컬을 UUID로 통일 (근본 해결, 땜질 금지).
14개 서비스 20곳 nanoid() → crypto.randomUUID() 일괄 교체.
nanoid 의존성 + electron.vite.config exclude 제거.
drizzle schema는 text PK 그대로 유지 (SQLite는 UUID 문자열 저장 가능).
## Bug 5: supabase_realtime publication 누락
- 증상: 로그인 직후 Realtime 채널 상태: TIMED_OUT
- 원인: initial_schema.sql이 transcripts 테이블만 publication에 추가.
데스크톱이 구독하는 meetings/history/dictionary는 누락 → postgres_changes 흐르지 않음.
- 픽스: 20260411000002_realtime_publication.sql 신규.
pg_publication_tables 카탈로그 체크 + 조건부 ADD TABLE (meetings/meeting_memos/
meeting_documents/history/dictionary 5개). supabase db push 적용.
## Bug 6: persistSession:false에서 realtime.setAuth 자동 전파 안 됨 (부분 픽스)
- 픽스: CloudSyncService.startRealtime()에 client.realtime.setAuth(access_token)
명시 호출 (채널 구성 이전).
- ⚠️ Bug 5+6 적용 후에도 Realtime 여전히 TIMED_OUT. 후속 조사 필요.
블로커 아님 — 주기 pull + Phase 3.3 auto push로 최종 일관성 유지.
## 실증 결과
- A 세션 자동 복원: Restored session for yunchan8804@gmail.com → DB 재오픈
- B push 경로: HistoryService created 56a767ac-... → Sync complete pushed=1 errors=0
- C 로그아웃 복귀: Realtime 종료 → users/_local/d3ro.db 복귀 → local mode
- D 재로그인 복원: 실증 A의 restore 경로와 동일, 같은 uuid DB 파일 보존
- E 웹 크로스 디바이스: Phase 3.3 이후로 지연 (Realtime 이슈 별건)
검증: desktop tsc --noEmit ✅, dev 재기동 ✅, push 최초 성공 ✅
빅뱅 Phase 5 실증 중 발견한 3개 버그 해결:
1. implicit flow 응답 무시:
- supabase-js가 persistSession:false 일 때 PKCE code_verifier 저장 못해
implicit flow로 fallback, fragment(#access_token=...&refresh_token=...)로
토큰 전달. handleDeepLink는 ?code=만 파싱하고 있어 무시됨.
- handleDeepLink에 fragment parser 추가 (access_token + refresh_token)
- CloudSyncService.handleAuthTokens() 신규 — setSession() 후
기존 _onAuthenticated SSOT로 수렴
- 에러 query/fragment(error, error_description) 상세 로깅
- 토큰 값은 로그에 안 찍히도록 (query=yes/no, fragment=yes/no 만)
2. Supabase RLS 무한 재귀:
- team_members_read_same_team 정책이 자기 테이블 재조회 →
Postgres RLS 엔진 무한 재귀 탐지 에러
- meetings / meeting_memos / meeting_documents / team_invites 정책이
team_members 서브쿼리 경유해서 전부 같이 터짐 + Realtime TIMED_OUT
- migration 20260411000001: SECURITY DEFINER 함수 2개
(user_team_ids, user_admin_team_ids) 신규 — Supabase 권장 패턴
- 영향 정책: team_members(4) + meetings(2) + meeting_memos(1) +
meeting_documents(2) + team_invites(1) 전부 함수 기반으로 재작성
- supabase db push 완료
3. LoginScreen stale 번들:
- Phase 1.5에서 import 제거했는데도 vite HMR/cache 어딘가에서
stale state 유지해서 렌더러에 계속 뜸
- LoginScreen.tsx 파일 자체 삭제 (vite 컴파일 대상 제거)
로그인 성공 확인: yunchan8804@gmail.com 으로 Google OAuth 완주 →
users/7da3dd02-9f2f-4ee9-a9b3-1c2c24875a93/d3ro.db 생성 → Initial sync 시작.
Phase 3 재정의: Supabase = source of truth 방향 철회.
로컬 entry point 철학 하에 "로컬이 SoT, 클라우드는 로그인 시 mirror"로 확정.
CloudSyncService._initialSync():
- _onAuthenticated 끝에 fire-and-forget 호출
- pushAll → pullAll 순차 실행
- signin: 익명 로컬 데이터를 사용자 계정으로 업로드
- restore: 다른 기기 변경 반영
- 실패 시 warn만, 수동 Sync 버튼으로 재시도 가능
types.ts:
- FeatureAccess.reason: 'login_required' 추가
- UpgradePromptEvent.reason: 'login_required' 추가
LicenseService:
- consumeQuota(): login_required 케이스 — promptUpgrade + D3ROError(TierRequired)
- promptUpgrade(): login_required 시 requiredTier='free'
(로그인만 하면 free로 바로 사용 가능)
- quota_exceeded 시 현재 tier 상위로 승격 제안
UpgradePromptModal:
- isLoginRequired 분기 — title/desc 교체
- Primary button: "로그인하기" → d3ro:open-settings event (tab=cloud)
i18n ko/en:
- license.loginRequired.title/desc/signIn 추가
비즈니스 모델 수정:
- 무료 로컬 모드 = 사용자 onboarding funnel 입구 (킬러 피처, 회원가입 0)
- 유료 클라우드 SaaS = 수익화 (OAuth 로그인 + pro + premium 모델 + sync)
- 이전 세션 SaaS [2] OAuth 강제 게이트 철회
db/index.ts:
- LOCAL_USER_ID = '_local' 상수 export
- openLocal() 헬퍼 — userData/users/_local/d3ro.db
- isLocalMode() 헬퍼
bootstrap.ts:
- database step 복원 (critical). openLocal() 호출로 앱 시작 시
로컬 DB 자동 오픈 — 로그인 없이 즉시 메인 UI 진입 가능
CloudSyncService._onSignOut():
- closeCurrent() 후 openLocal() 호출 — 로그아웃 시 로컬 모드 DB로 복귀
- 앱은 계속 로컬 모드로 동작 (entry point 철학)
App.tsx:
- AuthGate 상태머신 제거 (loading/login-required/authenticated/legacy)
- LoginScreen import 제거, 항상 AppLayout 렌더
- LoginScreen.tsx 파일은 유지 (추후 Welcome 화면 재활용 가능)
검증:
- desktop tsc --noEmit ✅
- desktop build ✅
- dev 런타임: [bootstrap] local database opened → Main window shown
- users/_local/d3ro.db 파일 생성 확인
이번 세션에서 한 일:
- 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차 섹션 추가
[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
- Node 22 + sox + supabase CLI 설치 (brew)
- npm install --force + @electron/rebuild@3 33.4.11 완료
- desktop/web typecheck + web build + api-client test 전부 통과
- supabase link --project-ref llnocwyqvhgwpdjcqqyw
- config.toml db.major_version 15→17 (원격 매치)
- db push: 8개 마이그레이션 전부 적용
- functions deploy: 10개 Edge Functions 전부 배포
- apps/web/.env.local + server/supabase/.env.local 생성 (gitignored)
- next build 재검증 — Supabase 런타임 주입 확인 (라우트 동적화)
묶음 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)