Skip to content

Feat/obs mode#61

Merged
lee-sihun merged 56 commits intomasterfrom
feat/obs-mode
Mar 10, 2026
Merged

Feat/obs mode#61
lee-sihun merged 56 commits intomasterfrom
feat/obs-mode

Conversation

@lee-sihun
Copy link
Member

No description provided.

lee-sihun and others added 30 commits March 7, 2026 15:23
- AppStoreData/SettingsState에 obs_mode_enabled, obs_port 필드 추가
- models/obs.rs: WS 프로토콜 메시지 타입 (ObsEnvelope, ObsInMessage, ObsBroadcast, KeyState enum 등)
- types/obs.ts: 프론트엔드 OBS 프로토콜 타입 정의
- Cargo.toml: tokio, tokio-tungstenite, futures-util 의존성 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- tokio-tungstenite 기반 WS 서버 (localhost 전용)
- broadcast 채널로 키 이벤트/설정/레이아웃 변경 릴레이
- 클라이언트별 seq 카운터 (gap 감지 지원)
- hello 핸드셰이크 (5초 타임아웃) → snapshot 전송 → 메인 루프
- compare_exchange로 start() 원자적 보장
- stop() 시 broadcast 채널 종료로 클라이언트 자연 종료
- Lagged 시 자동 snapshot 재전송

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs_bridge 필드를 AppState에 추가, initialize에서 생성
- obs_start/obs_stop/obs_status Tauri 커맨드 추가
- 키 이벤트 루프에서 broadcast_key_event 호출
- emit_settings_changed에서 broadcast_settings_diff 호출
- ObsBroadcast::Shutdown으로 stop 시 기존 클라이언트 세션 종료

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- refresh_obs_snapshot(): bootstrap_payload 직렬화 → cached_snapshot 갱신
- obs_broadcast_counters(): 카운터 상태 OBS 브로드캐스트
- obs_start 시 초기 스냅샷 캐싱 (신규 클라이언트 지원)
- preset_load/preset_load_tab 후 전체 스냅샷 재전송
- keys:counters emit 9개 지점에 obs_broadcast_counters 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- overlay/App.tsx에서 순수 렌더링 JSX를 OverlayScene으로 분리
- 키, 통계, 그래프, WebGL 트랙, 카운터 레이어 렌더링 공용화
- FALLBACK_POSITION을 OverlayScene에서 단일 소스로 export
- showPluginElements prop으로 Tauri 전용 플러그인 렌더링 제어
- OBS standalone 페이지에서 동일 렌더링 재사용 가능

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs/index.html + index.tsx: Vite 멀티 엔트리 OBS 페이지
- obs/App.tsx: WebSocket 연결 → 상태 관리 → OverlayScene 렌더링
- useObsWebSocket 훅: 자동 재연결, hello 핸드셰이크, 메시지 디스패치
- snapshot/key_event/settings_diff/counter_update 처리
- vite.config.ts에 obs 엔트리 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obsApi 모듈 추가 (start/stop/status Tauri 커맨드 래퍼)
- Settings.tsx에 OBS 모드 섹션 추가 (서버 시작/중지, 포트 입력, 상태 표시, URL 복사)
- 3초 주기 상태 폴링 (shallow 비교로 불필요한 리렌더 방지)
- 5개 언어 i18n 키 추가 (ko, en, zh-cn, zh-Hant, ru)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 불필요한 .to_string().into() → .to_string() 변환
- 미사용 메서드에 #[allow(dead_code)] 추가 (v2 예정)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- models/obs.rs: ObsEnvelope, ObsInMessage 미사용 필드 dead_code 허용
- commands/app/obs.rs, state/app_state.rs: cargo fmt 적용
- docs/obs-mode-design.md: v1 완료 항목 표시 및 v2 로드맵 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs_bridge.rs: TCP peek로 HTTP/WS 분기, 정적 파일 서빙 구현
- obs_start: AppHandle에서 OBS static dir 경로 자동 탐색
- 모든 레이아웃 변경 커맨드에 refresh_obs_snapshot() 추가
  (keys_set_mode, keys_update, positions_update, stat/graph_positions,
   custom_tabs_create/delete/select/restore, reset_all/mode, note_tab)
- emit_settings_changed에서 cached_snapshot 증분 갱신
- refresh_obs_snapshot이 캐시 갱신 + broadcast 통합
- 프론트엔드 URL 복사: ws:// → http://
- OBS 페이지 WS URL: window.location 기반 자동 연결

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- HTTP path traversal 방어 강화 (절대경로/드라이브/역슬래시 거부 + canonicalize 재검증)
- X-Content-Type-Options: nosniff 헤더 추가
- stop→start 포트 경쟁 조건 수정 (server_handle JoinHandle 대기)
- server_loop running 플래그 경쟁 조건 수정 (stop()에서만 관리)
- port=0 입력 시 실제 바인딩 포트 감지 (local_addr)
- obs/index.html 엔트리 확장자 수정 (jsx→tsx)
- innerHTML XSS → textContent 기반 렌더링
- ObsInMessage/HelloPayload dead code 제거
- v3 로드맵 추가 (설계 문서)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- settings_update 경로에 obs_mode_enabled/obs_port 와이어링
- Zustand 스토어에 obsModeEnabled/obsPort 추가 + 부트스트랩 매핑
- handleObsToggle에서 시작 성공 후 settings_update 호출 (실패 시 잔류 방지)
- 부팅 시 obs_mode_enabled=true이면 자동 시작, 실패 시 false로 복구
- Codex 리뷰 반영: obsPort useState 동기화, 매직넘버 제거, 호출 순서 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs_start 시 오버레이 자동 숨김, obs_stop 시 복원
- overlay_set_visible에 OBS 모드 중 수동 토글 차단 가드
- ToggleOverlay 데몬 핸들러에 OBS 모드 가드
- auto_start_obs 실패 시 오버레이 실제 윈도우까지 복원
- OBS 클라이언트 KPS 1초 슬라이딩 윈도우 (50ms 샘플링)
- held-key dedup via activeKeys Set
- onCounterUpdate에서 tracker.total 재계산

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OBS 실행 중 브라우저 소스 설정 가이드 텍스트 표시
- 오버레이 숨김 경고 메시지 (amber 색상)
- role="status" 접근성 속성 추가
- 5개 locale (en/ko/zh-cn/zh-Hant/ru) 문자열 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- keyDisplayDelayMs: 메인 오버레이와 동일한 딜레이 패턴 적용
- per-key noteEffectEnabled: 키별 노트 효과 on/off 필터링
- onSnapshot에서 딜레이 타이머 정리 + ref 즉시 동기화
- ESLint cleanup ref 경고 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
overlay/obs App.tsx에서 ~330줄의 중복 레이아웃 계산 코드를
hooks/shared/useLayoutComputation.ts의 순수 함수로 통합.
pluginElements는 optional param으로 overlay 전용 지원 유지.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 서버 start() 시 UUID v4 세션 토큰 생성, stop() 시 클리어
- WS hello 핸드셰이크에서 토큰 검증, 불일치 시 AUTH_FAILED 에러 전송
- OBS 페이지는 URL query param (?token=xxx)에서 토큰 추출하여 전송
- Settings UI에서 URL 복사 시 토큰 포함
- 클라이언트 AUTH_FAILED 수신 시 재연결 루프 중단
- bind 실패 시 stale 토큰 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CSS 커맨드(toggle/reset/set_content/load) 변경 시 OBS 클라이언트에 settings_diff 전파
- CSS 핫리로드 워처에서도 OBS 브릿지 알림 추가
- OBS App.tsx에 CSS 주입 로직 (cssStateRef + applyCssToDOM)
- notify_obs_settings_diff: 전체 스냅샷 대신 settings_diff만 전송하여 키 상태/KPS 리셋 방지

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- /media/<base64url-path>?token=xxx 라우트로 로컬 미디어 파일 서빙
- 세션 토큰 검증으로 무인증 파일 유출 방지
- 허용 확장자 화이트리스트 (이미지/영상/폰트만)
- Access-Control-Allow-Origin: * 제거 (same-origin만)
- resolveImageSource: Tauri API 실패 시 OBS HTTP fallback
- guess_mime 확장 (gif/webp/mp4/webm/ttf/otf)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs_bridge에 dev_url 필드 및 set_dev_url() 추가
- HTTP 요청 시 static_dir 없으면 dev_url로 302 리다이렉트
- obs_start에서 cfg!(debug_assertions) 시 자동 dev_url 설정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
확장자가 있는 파일 요청(.js, .css 등)이 실패할 때 index.html을
text/html로 반환하여 MIME type 불일치 에러 발생.
확장자 있는 요청은 SPA fallback 대신 404 반환하도록 변경.

또한 obs-mode-design.md의 v2 기준 섹션들을 v3 구현 현황에 맞게
일괄 업데이트 (7.2 모드 전환, 8.1 포트 보안, 8.2 기능 제약,
4.2 struct 필드, 4.3 버전, 5.2 레이아웃 공유, 11.2 #11 상태).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
stale 빌드 디렉토리가 남아있을 때 resolve_obs_static_dir()이
오래된 경로를 찾아 dev_url 리다이렉트 분기를 타지 않는 문제.
cfg!(debug_assertions)일 때 항상 Vite dev server를 우선 사용.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
빌드된 obs/index.html이 ../assets/obs-xxx.js를 참조하는데,
static_dir이 obs/ 하위를 가리켜서 assets/ 접근 불가 → 빈 화면 발생.
static_dir을 dist/renderer/ (부모 디렉토리)로 변경하고,
"/" 요청을 obs/index.html로 매핑하도록 수정.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- A/B/C 접근 방식 비교 및 B 방식(OverlayHost adapter) 확정
- OverlayHost 인터페이스 정의, 구현 계획, 범위 제한 명시
- #11 상태를 "설계 확정 / 구현 대기"로 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
obs/App.tsx의 렌더링 로직(키 상태, KPS, 딜레이, CSS, 레이아웃)을
useOverlayRuntime 훅으로 추출. obs/App.tsx는 405줄에서 34줄로 축소.
향후 TauriHost 마이그레이션 시 overlay/App.tsx에서도 동일 훅 사용 가능.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- static_dir 디스크 파일 서빙을 AssetFetcher(asset_resolver) 기반으로 교체
- dev 모드 리다이렉트에 port/token query param 추가 (WS 포트 불일치 해결)
- useObsWebSocket의 try/catch 범위를 JSON 파싱만으로 축소하여 콜백 에러 전파

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OverlayHost adapter(B) → Tauri IPC Shim(C) 방식으로 전환.
invoke/listen 프리미티브를 WS로 교체하여 overlay 코드 변경 없이
OBS 호환성 달성. useOverlayRuntime 중복 로직 완전 해소 설계.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
lee-sihun and others added 26 commits March 8, 2026 11:54
…64url

§12 설계 기반으로 ipcShim.ts를 재작성:
- 하드코딩 NO_OP_COMMANDS → hello_ack에서 수신하는 denyList로 교체
- isDenied() 함수: | suffix → prefix 매칭, 아니면 exact 매칭
- tauri_event WS 메시지 수신 처리 추가
- convertFileSrc → OBS HTTP /media/ base64url(no-pad) 인코딩
- disposeIpcShim → pending RPC reject 처리
- WS 끊긴 상태에서 RPC 즉시 reject
- 구 버전 백엔드 호환: denyList 미수신 시 DEFAULT_DENY_LIST 폴백
- obs.ts: HelloAckPayload에 denyList, ObsMessageType에 invoke_request/invoke_response/tauri_event 추가
- 설계 문서(§12) deny 리스트 일원화 반영

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- models/obs.rs: HelloAckPayload에 deny_list 필드, InvokeRequestPayload 구조체 추가
- obs_bridge.rs: DENIED_WS_COMMANDS 상수 + is_denied() + build_deny_list()
- obs_bridge.rs: AppHandle 저장, handle_invoke_request() → webview.on_message() 디스패치
- obs_bridge.rs: unbounded mpsc 채널로 RPC 응답 → invoke_response WS 메시지 전송
- obs_bridge.rs: hello_ack에 deny_list 포함
- obs_bridge.rs: 크로스 플랫폼 로컬 URL (Windows: http, macOS/Linux: tauri://)
- obs_bridge.rs: 파싱 실패 시 에러 응답 (클라이언트 대기 방지)
- deny list 보강: obs_start/stop, 파일 대화상자, preset_load 등 추가
- ipcShim.ts: DEFAULT_DENY_LIST 동기화

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- register_event_forwarding()으로 22개 Tauri 이벤트를 app.listen() 구독
- ObsBroadcast::TauriEvent → tauri_event WS 메시지로 변환
- 리스너 lifecycle 관리 (stop 시 unlisten, 중복 호출 시 기존 해제)
- auto_start_obs 경로에 set_app_handle + register_event_forwarding 추가
- keys:counter 이벤트명 수정 (keys:counter-changed → keys:counter)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s 추가

- Rust/TS BootstrapPayload 타입에 3개 필드 추가
- bootstrap_payload()에서 스토어 데이터 populate
- ipcShim.ts에서 unsafe cast 제거, 정식 타입 접근으로 전환

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
IPC Shim이 overlay/App.tsx를 직접 사용하므로 중간 레이어 불필요:
- obs/App.tsx (useObsWebSocket + useOverlayRuntime 래퍼)
- hooks/obs/useObsWebSocket.ts (레거시 WS 클라이언트)
- hooks/shared/useOverlayRuntime.ts (스냅샷 → signal 브릿지)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- §13 Tier 1 전체 ✅ 완료 표시 + 커밋 해시 기록
- Tier 3 알려진 이슈 추가 (input:raw 이중 전달, tabCss resync)
- §5.1 레거시 파일 삭제 반영
- §8.2 커스텀 JS/플러그인 지원 상태 업데이트
- v4 마일스톤 완료 기록

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tier 3: 초기 빈화면 버그 수정
- invoke_request 필드명 일치: reqId→requestId(String), cmd→command
- invoke_response 필드명 일치: ok→result, err→error
- 에러 폴백에서 requestId를 string으로 추출

Tier 2: WS 프로토콜 통합
- ObsBroadcast enum에서 KeyEvent, SettingsDiff, LayoutDiff, CounterUpdate 제거
- KeyState, KeyEventPayload 타입 삭제
- broadcast_key_event(), broadcast_settings_diff(), broadcast_counter_update() 삭제
- app_state.rs에서 직접 broadcast 호출 제거 (캐시 갱신만 유지)
- ipcShim.ts에서 key_event, settings_diff, counter_update 전용 핸들러 제거
- 모든 이벤트는 register_event_forwarding()이 tauri_event로 자동 포워딩

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ipcShim.ts: tauri_event 핸들러에서 settings:changed, keys:counters
  수신 시 snapshotCache를 증분 갱신 (getter 폴백 정합성 유지)
- obs.ts: KeyEventPayload, ObsMessageType 미사용 타입 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- handleCacheCommand + snapshotCache 제거 (모든 invoke는 WS RPC로)
- tauri_event 증분 캐싱 분기 제거 (settings:changed, keys:counters)
- snapshot 개별 이벤트 디스패치 제거 (12개 dispatchEvent 호출)
- DEFAULT_DENY_LIST 하드코딩 폴백 제거 (백엔드 단일 책임)
- onWsMessage: tauri_event/invoke_response/snapshot 모두 generic 처리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- register_event_forwarding에서 app.listen → app.listen_any로 변경
  (emit_to로 특정 윈도우에 보낸 이벤트도 OBS에 포워딩)
- CLAUDE.md, AGENTS.md에 OBS 이벤트 포워딩 등록 가이드 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs_hide_overlay: hide 대신 window.close()로 오버레이 destroy (메모리/GPU 절약)
- overlay_obs_close 플래그로 CloseRequested 핸들러에서 OBS close 허용
- obs_restore_overlay: 플래그 리셋 후 set_overlay_visibility(true)로 윈도우 재생성
- auto_start_obs: OBS 모드 부팅 시 오버레이 생성 건너뛰기 (create→destroy 낭비 방지)
- obs:status 이벤트 emit + 프론트 이벤트 구독 (obsApi.onStatus)
- Settings.tsx: 전체 3초 폴링 → 이벤트 구독 + clientCount 5초 폴링으로 전환
- SettingTool: OBS 모드 시 오버레이 토글 버튼 비활성화 (disabled + 전용 툴팁)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bridge.start() 성공 후 refresh_obs_snapshot()을 호출하지 않아
OBS 브라우저 연결 시 키 위치/노트 효과 등 초기 상태가 비어있던 문제 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- obs_stop을 async fn으로 변경 (sync 컨텍스트에서 WebviewWindowBuilder::build() 시
  메시지 루프 차단으로 빈 창이 생성되는 Windows WebView2 문제 수정)
- overlay_obs_close 플래그 제거 — destroy()는 CloseRequested 이벤트를 발생시키지
  않으므로 별도 플래그 불필요
- obs_hide_overlay: close() → destroy()로 변경, store.overlay_visible 미변경
  (ensure_overlay_window 재생성 시 원래 상태 기준으로 show/hide 결정)
- invoke_request 디스패치: overlay 부재 시 main 윈도우 fallback 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
obs_token을 AppStoreData에 저장하여 앱 재시작 시 동일 토큰 재사용.
토큰 재생성 커맨드(obs_regenerate_token) + 확인 모달 UI 추가.
OBS 브릿지 바인드를 0.0.0.0으로 변경하여 LAN 접근 허용.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
포트 입력 UI와 관련 설정 파이프라인(SettingsState, SettingsPatchInput,
SettingsDiff, store sync, bootstrap)에서 obs_port를 제거.
obs_bridge.start()가 기본 포트 시도 후 +1~+9 순차 fallback하여
자동으로 가용 포트를 찾고 store에 저장. AppStoreData.obs_port는
내부 저장용으로 유지.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- §1(토큰 영구 저장), §5(자동 포트 fallback) 구현 완료 반영
- codex-review/plan/debug 스킬에 오버엔지니어링 피드백 필터링 규칙 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- uninit_vec: Vec::with_capacity + set_len → vec![0u8; size]
- 불필요한 raw pointer 캐스트 제거 (ipc.rs, app_state.rs)
- 불필요한 참조 제거 (main.rs)
- unused import 제거 (load.rs, app_state.rs)
- &PathBuf → &Path (main.rs)
- snapshotReceived → _snapshotReceived (ipcShim.ts)
- cargo clippy --fix 자동 수정 반영 (obs_bridge.rs, windows.rs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- map_or(false, ...) → is_some_and(...) (engine.rs, obs_bridge.rs)
- 불필요한 return 제거 (update.rs, daemon/mod.rs)
- let _ = open_devtools() → open_devtools() (system.rs)
- .iter().cloned().filter() → .iter().filter().cloned() (keys.rs)
- 범위 비교 → contains() (lib.rs)
- Default impl → #[derive(Default)] + #[default] (models/mod.rs)
- field_reassign_with_default → 구조체 리터럴 (load.rs, store.rs, image.rs)
- #[allow] 적용: too_many_arguments, enum_variant_names, module_inception

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
capability 파일에서 불필요한 remote URL 패턴 제거 및 dev capability를
debug_assertions 빌드에서만 런타임 등록하도록 변경.

- main.json, dmnote-dev.json에서 remote URL 섹션 제거 (local:true만 유지)
- tauri.conf.json에서 dmnote-dev capability 제거 (dev 빌드 시 런타임 등록)
- register_dev_capability()를 cfg!(debug_assertions) 가드로 보호
- 프로파일링 코드 정리 (counting_alloc, mem_info, mem_log 등 제거)
- dhat 의존성 및 heap-profile 프로필 제거

결과: Private Bytes 202.6MB → 7.3MB (96% 감소), 바이너리 ~2MB 축소

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
오버레이가 숨겨진 상태로 OBS 모드 진입→해제 시 obs_restore_overlay가
윈도우를 재생성하지 않아, 이후 sync 커맨드에서 WebView2 빌드가
메시지 루프를 블로킹하는 문제 수정. prev=false일 때도
ensure_overlay_window를 호출하여 async 컨텍스트에서 미리 재생성.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
show_menu_on_left_click(false)로 좌클릭 메뉴 비활성화 후,
on_tray_icon_event에서 Left+Up 클릭 시 show_main_window() 호출.
우클릭 컨텍스트 메뉴(Settings/Quit)는 기존 동작 유지.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Start/Stop 버튼 → Checkbox 토글로 변경 (다른 설정과 일관)
- 버튼 좌측 배치 → 라벨 좌 + 컨트롤 우 패턴 적용
- Copy URL/Regen Token 버튼 항상 렌더링 + disabled 스타일
- 상태 텍스트를 서브행 좌측으로 이동 (CSS 섹션 패턴)
- 안내 텍스트 항상 표시 + off 시 dimmed 처리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lee-sihun lee-sihun merged commit 6be6699 into master Mar 10, 2026
1 check passed
@lee-sihun lee-sihun deleted the feat/obs-mode branch March 10, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant