본문으로 건너뛰기

아키텍처

이 프로젝트는 계산 로직, 모듈 호스팅, 개별 기능 UI를 분리합니다. 목표는 시계 기능을 유지하면서도 다른 개발자가 만든 모듈을 같은 캔버스에 추가할 수 있게 하는 것입니다.

계층 구조

App
└─ InfiniteCanvas
├─ Canvas module runtime
│ ├─ common container chrome
│ ├─ common drag / scale / remove behavior
│ └─ selected module Module
└─ Extension panel
├─ selected module Extension
└─ common module settings

주요 책임

파일책임
src/App.tsx등록된 모듈 정의를 캔버스에 전달하고, 모듈 인스턴스, 선택 상태, 캔버스 view, undo/redo 히스토리를 관리합니다.
src/components/InfiniteCanvas.tsx줌, 모듈 이동, 모듈 선택, 오른쪽 확장 패널 렌더링, 공통 undo/redo 버튼 표시를 담당합니다.
src/modules/types.ts외부 모듈이 따라야 하는 공통 타입 계약을 정의합니다.
src/modules/registry.ts앱이 사용할 모듈 정의 목록을 등록합니다.
src/components/ClockModule.tsx시계 모듈의 Provider, 캔버스 본문, 확장 패널을 정의합니다.
src/components/PostItModule.tsx포스트잇 모듈의 Provider, 캔버스 본문, 확장 패널을 정의합니다.
src/features/clock/*시간 계산, 드래그 각도 계산, 시계 상태 컨트롤러를 담당합니다.
src/services/*영어 시간 읽기와 브라우저 음성 재생을 담당합니다.

상태 흐름

App은 어떤 모듈 인스턴스가 존재하는지, 어떤 모듈이 선택되었는지, 공통 캔버스 상태인 view, 각 모듈의 위치/확대/회전, 각 모듈의 내부 state를 모두 관리합니다. 이 AppState가 undo/redo의 단일 스냅샷입니다.

개별 모듈의 Provider는 더 이상 사용자 상태를 소유하지 않습니다. Providerinstance.state를 읽고, 상태 변경이 필요할 때 updateModuleState를 호출합니다. 캔버스의 Module과 오른쪽 Extension은 같은 Provider 아래에서 렌더링되기 때문에 동일한 App 소유 상태를 공유합니다.

hover, pointer drag 중 여부, 포인터 캡처 같은 복원할 필요 없는 UI transient 상태만 컴포넌트 로컬 useStateuseRef에 둘 수 있습니다.

시작과 끝의 경계가 분명한 연속 액션은 히스토리 transaction으로 묶습니다. 예를 들어 모듈 이동, 회전, 마우스 리사이즈, 시계 바늘 이동은 pointermove 동안 현재 화면만 갱신하고, pointerup/cancel/blur 시점에 하나의 undo/redo 항목으로 확정합니다. 휠 줌은 입력이 잠시 멈춘 시점, 슬라이더와 숫자 입력은 blur/Enter 같은 편집 종료 시점에 확정합니다.

캔버스와 확장 패널 분리

캔버스 카드에는 모듈의 핵심 작업 화면만 둡니다. 시계 모듈에서는 아날로그 시계 자체가 여기에 해당합니다.

선택된 모듈의 확장 패널에는 학습 안내, 현재 시간 정보, 명령 버튼, 세부 설정처럼 컨텍스트성 UI를 둡니다. 공통 확대 설정은 런타임이 같은 패널 아래에 자동으로 추가합니다. 이렇게 분리하면 캔버스가 복잡해지지 않고, 모듈별 부가 기능과 공통 기능을 일관된 위치에 표시할 수 있습니다.