Appearance
업데이트 노트
Canvas 프로젝트의 날짜별 커밋·PR·파일 변경 이력입니다. 변경 범위, 설계 배경, 관련 파일 경로를 일자별로 확인할 수 있습니다.
프로젝트 개요 · GitHub 저장소 · 배포 문서
전체 요약
문서 범위
- 데이터 출처:
main브랜치git log및 PR 머지 커밋 - 기간: 2026-05-18 ~ 2026-06-01 (활동 8일, 커밋 22건)
- PR: #15 ~ #51 (커밋 메시지에 기록된 머지 PR 17건)
- 저장소: github.com/ToKyun02/canvas
누적 변경량 (일자별 합산)
| 날짜 | 커밋 | 추가 줄 | 삭제 줄 | 변경 파일(고유) | 주요 작업 |
|---|---|---|---|---|---|
| 05-18 | 5 | 15,625 | 3,060 | 99 | 모노레포 부트스트랩 · Docker · CI 레이블 |
| 05-19 | 3 | 400 | 35 | 13 | TanStack Router · 협업 템플릿 |
| 05-20 | 3 | 1,745 | 38 | 31 | Fabric 캔버스 · Command · 카메라 |
| 05-21 | 2 | 1,665 | 64 | 27 | 텍스트 노드 · 속성 패널 |
| 05-22 | 5 | 1,457 | 327 | 34 | Store-first 리팩터 · 레이블 · persist · 휠 UX |
| 05-27 | 1 | 3,776 | 10 | 29 | VitePress 문서 · Firebase CI |
| 05-28 | 1 | 306 | 229 | 26 | dead code 제거 · 문서 동기화 |
| 06-01 | 2 | 2,234 | 591 | 68 | Google OAuth · Prisma · lint-staged |
개발 단계 개요
PR 맵 (번호 → 주제)
| PR | 날짜 | 제목 | Δ 줄 (대략) | 핵심 경로 |
|---|---|---|---|---|
| #15 | 05-18 | Docker Compose | +1,823 / −2,041 | docker-compose*.yml, Dockerfile.dev |
| #17 | 05-19 | pnpm 버전 고정 | +10 / −20 | apps/*/Dockerfile.dev |
| #19 | 05-19 | PR 템플릿 | +13 | .github/PULL_REQUEST_TEMPLATE.md |
| #21 | 05-19 | TanStack Router | +377 / −15 | apps/web/src/routes/ |
| #22 | 05-20 | 캔버스 초기화 | +1,138 / −21 | features/canvas/, /canvas 라우트 |
| #23 | 05-20 | Command & Shortcut | +364 / −11 | stores/commands/ |
| #27 | 05-20 | 카메라 | +243 / −6 | useCanvasCamera, zoomAtPoint |
| #29 | 05-21 | 텍스트 노드 | +1,657 / −64 | stores/nodes/, Sidebar |
| #33 | 05-21 | Backspace 삭제 | +8 | stores/commands/index.ts |
| #34 | 05-22 | 노드 생성 버그 | +674 / −243 | useCanvasNodes, nodesSlice |
| #39 | 05-22 | default fill | +2 / −2 | stores/nodes/text/ |
| #40 | 05-22 | 레이블 오버레이 | +482 / −25 | features/canvas/labels/ |
| #42 | 05-22 | 상태 저장 | +135 / −17 | persistence/appStorage.ts |
| #43 | 05-22 | 휠 UX | +164 / −40 | handleViewportWheel.ts |
| #45 | 05-27 | 문서·호스팅 | +3,776 / −10 | apps/docs/, hosting-docs.yml |
| #47 | 05-28 | 정리 | +306 / −229 | docs + web 정리 |
| #49 | 06-01 | OIDC | +2,007 / −582 | apps/api/src/auth/, Prisma |
| #51 | 06-01 | lint-staged | +227 / −9 | .husky/, lint-staged.config.mjs |
2026-06-01 — 인증 · 코드 품질 자동화
개발 초점: Google OAuth 기반 사용자 인증 도입, 커밋 시점 ESLint·Prettier 자동 적용.
| 지표 | 값 |
|---|---|
| 커밋 | 2 · PR #49, #51 |
| 변경량 | +2,234 / −591 · 66개 경로 |
#49 OIDC / Google 로그인
변경 배경
캔버스 데이터는 클라이언트 localStorage에만 저장되고 있었습니다. 사용자 식별과 이후 서버 연동을 위해 인증 API 최소 구현(OAuth + JWT)이 추가되었습니다.
구현 요약
| 레이어 | 추가·변경 | 설명 |
|---|---|---|
| DB | prisma/schema.prisma, 2개 migration | User 모델 — email, provider/providerId, avatarUrl |
| Auth | auth.module.ts, strategies, guard | Google OAuth 2.0 → JWT 발급 |
| API | auth.controller.ts, users.controller.ts | /auth/google, callback, POST /auth/logout, GET /users/me |
| Web | routes/login/, routes/index.tsx | 로그인 진입·홈 연동 |
| Infra | docker-compose.dev.yml | api 서비스에 GOOGLE_*, JWT_SECRET 주입 |
| Docs | 아키텍처·env·docker 문서 15+ 페이지 | 인증 반영 |
인증 플로우
핵심 코드 — 신규/기존 사용자 upsert 후 JWT
typescript
// apps/api/src/auth/auth.service.ts
let user = await this.prisma.user.findUnique({
where: { providerId: googleUser.providerId },
});
if (!user) {
user = await this.prisma.user.create({
data: {
/* email, name, avatarUrl, provider */
},
});
}
return {
accessToken: this.jwtService.sign({ sub: user.id, email: user.email }),
user,
};쿠키·CORS
main.ts:cookieParser(),enableCors({ origin: ['http://localhost:3001'], credentials: true })- Callback에서
res.cookie('access_token', …, { httpOnly: true })후 web으로 redirect
스키마 (Prisma)
prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
avatarUrl String?
provider String
providerId String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}함께 반영된 변경
- shadcn UI 컴포넌트 포맷 정리 (button, sheet, sidebar 등)
apps/docs내 OAuth·DB 관련 문서 갱신
#51 pre-commit lint & format
변경 배경web / api / packages가 패키지별 ESLint 설정을 사용하므로, 변경된 경로에 해당하는 패키지만 검사하도록 lint-staged를 구성했습니다.
| 파일 | 역할 |
|---|---|
.husky/pre-commit | pnpm exec lint-staged 실행 |
lint-staged.config.mjs | 경로 prefix별 eslint --fix + 전역 prettier --write |
lint-staged 규칙
*.{ts,tsx,md}→ Prettierapps/web/**→pnpm --filter web exec eslint --fixapps/api/**→pnpm --filter api exec eslint --fixpackages/api/**→pnpm --filter @repo/api exec eslint --fix
2026-06-01 (이후) — 워크스페이스 · 노드 서버 저장
개발 초점: 인증(#49) 이후 캔버스 문서를 PostgreSQL에 영속화하고, URL·접근 제어를 워크스페이스 단위로 정리.
INFO
이 섹션은 main에 아직 머지되지 않은 작업 기준입니다.
배경
#42에서 nodes/nodeOrder를 localStorage document 그룹에 저장했으나, OAuth(#49) 이후 사용자·워크스페이스 단위 서버 저장으로 이전했습니다. localStorage에는 UI 설정(isPropertiesSidebarOpen, isVisibleNodeLabels)만 남깁니다.
DB · API
| 모델 / 엔드포인트 | 설명 |
|---|---|
Workspace, WorkspaceMember, Node | Prisma 스키마 + migration |
GET/POST /workspaces | 목록·생성 |
GET /workspaces/:id | 상세 (멤버십 확인) |
GET/PUT /workspaces/:id/nodes | 문서 스냅샷 load/save (Node.data JSON) |
WorkspacesService.syncNodes: transaction으로 deleteMany + upsert. 비멤버 → 403, 미존재 → 404.
Web
| 경로 | 역할 |
|---|---|
features/workspace/ | API 클라이언트, WorkspaceHub, useWorkspaceDocument |
routes/canvas/index.tsx | 워크스페이스 목록·생성 (/canvas) |
routes/canvas/$workspaceId.tsx | 에디터 + beforeLoad 인증·권한 검사 |
useCanvasHydration.ts | 서버 로드 후 Fabric 복원 (nodesLoaded + workspaceId) |
appStorage.ts | document 그룹 제거 — UI 설정만 persist |
저장 흐름
Dev tooling
package.json:docker:prisma:studio— Prisma 7 호환 (--browser none,--hostname제거)apps/docs: 아키텍처·가이드·온보딩 문서 전반 갱신
2026-05-28 — 리팩터링 · 문서 동기화
개발 초점: 뷰포트·영속성 관련 미사용 코드 제거, 기술 문서를 현재 코드 동작과 일치시킴.
| 지표 | 값 |
|---|---|
| 커밋 | 1 · PR #47 |
| 변경량 | +306 / −229 · 26개 경로 |
제거·단순화 (web)
| 경로 | 내용 |
|---|---|
handleViewportWheel.ts | 중복 분기 56줄 삭제 — 단일 책임 유지 |
useCanvasViewportWheel.ts | 훅 내부 보일러플레이트 축소 |
appStorage.ts | 미사용 persist 키·헬퍼 제거 |
assignRef.ts | 미사용 유틸 파일 삭제 |
hydrateCanvas.ts, useFabricCanvas.ts | 불필요 의존·dead import 정리 |
routes/canvas/index.tsx | 사용하지 않는 UI/상태 연결 제거 |
문서 갱신 (docs)
| 문서 | 갱신 포인트 |
|---|---|
architecture/node-system.md | +133줄 — NodeDefinition·Fabric 동기화 상세 |
architecture/frontend.md | 라우트·feature 폴더 구조 |
guide/canvas-usage.md, adding-nodes.md | 실제 단축키·도구 흐름 |
reference/commands.md, env.md | 커맨드 ID·환경 변수 목록 |
참고
이 PR은 기능 코드 정리와 함께 아키텍처·가이드 문서를 갱신하여, 문서만으로도 구현 상태를 추적할 수 있도록 맞춘 변경입니다.
2026-05-27 — 기술 문서 사이트 · CI 배포
개발 초점: VitePress 기반 문서 사이트 신설, main 브랜치 푸시 시 Firebase Hosting 자동 배포.
| 지표 | 값 |
|---|---|
| 커밋 | 1 · PR #45 |
| 변경량 | +3,776 / −10 · 29개 경로 |
신규 apps/docs 구조
apps/docs/
├── .vitepress/config.ts # nav, sidebar, mermaid, lastUpdated
├── index.md # 홈 Hero
├── onboarding/ (4페이지) # welcome, setup, first-week, codebase-tour
├── guide/ (5페이지) # getting-started, docker, canvas-usage, …
├── architecture/ (6페이지) # overview, monorepo, frontend, node-system, …
└── reference/ (3페이지) # commands, env, scriptsCI — hosting-docs.yml
| 트리거 | 동작 |
|---|---|
push → main + apps/docs/** 변경 | VitePress build → Firebase Hosting deploy |
workflow_dispatch | 수동 재배포 |
- pnpm 8.15.5, Node 24, concurrency로 중복 배포 취소
- 배포 URL: canvas-doc.web.app
2026-05-22 — 에디터 안정화 · UX 확장 (5 PR)
개발 초점: 텍스트 노드 생성 경로 재설계, DOM 레이블·localStorage 영속성·뷰포트 휠 처리 개선.
#34 텍스트 노드 생성 버그 수정 · 아키텍처 전환
| 지표 | +674 / −243 · 22 files |
이전 동작placement.ts에 배치·Fabric 생성·선택 로직이 혼재하여, 텍스트 노드 생성 시 store와 캔버스 상태가 어긋나거나 중복 객체가 남는 문제가 있었습니다.
변경 후
| 신규/강화 | 책임 |
|---|---|
useCanvasNodes.ts (129줄 신규) | store nodes ↔ Fabric 객체 생성·갱신·삭제 |
nodesSlice.ts | addNode, updateNode, removeNode — 데이터 진실 공급원 |
canvasSync.ts | 편집 중(isCanvasInteracting) 동기화 스킵 |
textboxScaling.ts (144줄) | Fabric Textbox 리사이즈 시 font/width 보정 |
stores/nodes/fabric.ts | applyBaseNodeFields, readBaseNodeFields 공통화 |
placement.ts | 223줄 → 대폭 축소 — 배치만 담당 |
적용된 설계: Fabric 객체는 View, Zustand nodes는 Model로 분리. 이후 레이블·persist 구현의 기준이 됨.
#39 text node default fill
stores/nodes/text/index.ts— 기본fill/color2줄 조정- 다크/라이트 테마에서 텍스트 가독성 개선
#40 노드 레이블 오버레이
| 지표 | +482 / −25 · 10 files |
설계 선택
Fabric 기본 텍스트만으로는 줌·편집 모드와 레이블 UI가 충돌하기 쉽습니다. HTML 오버레이를 사용해 노드 이름 표시·드래그 이동을 DOM 레이어에서 처리합니다.
| 파일 | 역할 |
|---|---|
NodeLabelsOverlay.tsx | nodeOrder 순회, zoom/pan에 맞춰 screen 좌표 계산 |
NodeLabel.tsx (174줄) | 인라인 편집, 선택 하이라이트 |
coords.ts | scene ↔ screen 변환 |
moveNodeOnCanvas.ts | 레이블 드래그 → store position 갱신 |
동작
canvas.on('object:moving')등 Fabric 이벤트로 레이블 위치 재계산isVisibleNodeLabels플래그로 표시 on/off (이후 persist 대상)
#42 캔버스 상태 저장
| 지표 | +135 / −17 · 10 files |
persist 설계 (당시 #42 기준)
typescript
// apps/web/src/stores/persistence/appStorage.ts (2026-05-22 머지 시점)
export const PERSIST_GROUPS = {
document: ['nodes', 'nodeOrder'] as const,
preferences: ['isPropertiesSidebarOpen', 'isVisibleNodeLabels'] as const,
};
export const APP_STORAGE_KEY = 'canvas-app-v1';
export const APP_STORAGE_VERSION = 2;이후 변경
2026-06-01 이후 워크스페이스 작업에서 document 그룹(nodes, nodeOrder)은 제거되었고, 노드는 PostgreSQL Node 테이블에 저장됩니다. 현재 persist 대상은 preferences만입니다. → 워크스페이스 · 노드 서버 저장
| 구성요소 | 역할 (#42 시점) |
|---|---|
zustand/middleware persist | partializeAppState로 필요 필드만 저장 |
useCanvasHydration.ts | Fabric 캔버스 준비 후 hydrateCanvas 1회 실행 |
hydrateCanvas.ts | store 노드 → Fabric 객체 재생성 |
저장 범위에서 제외: zoom, position — 세션 시작 시 뷰포트는 초기화됨 (향후 사용자 설정으로 확장 가능).
#43 zoom·pan vs 브라우저 제스처
| 지표 | +164 / −40 · useCanvasCamera 39줄 삭제 후 책임 분리 |
이전 동작
캔버스 영역에서 휠 입력 시 브라우저 페이지 스크롤·시스템 제스처가 함께 동작하고, 입력 필드 포커스 중에도 줌이 적용되는 문제.
변경 후
| 파일 | 내용 |
|---|---|
useCanvasViewportWheel.ts | non-passive wheel 리스너, preventDefault |
handleViewportWheel.ts | mod+wheel → zoomAtPoint, shift+wheel → 수평 pan, 그 외 수직 pan |
hasEditableFocus() | input/textarea/contenteditable 포커스 시 줌·pan 무시 |
isCanvasInteracting() | Fabric 텍스트 편집 중에는 뷰포트 변경 차단 |
typescript
// handleViewportWheel.ts (요지)
if (options?.canvas && isCanvasInteracting(options.canvas)) return;
if (e.metaKey || e.ctrlKey) {
setViewport(zoomAtPoint(zoom, position, anchor, factor));
} else if (e.shiftKey) {
panBy({ x: -e.deltaY, y: 0 });
} else {
panBy({ x: 0, y: -e.deltaY });
}2026-05-21 — 텍스트 노드 · 속성 패널
| 지표 | 2 커밋 · +1,665 / −64 |
#29 텍스트 노드 · Properties Sidebar
| 지표 | +1,657 / −64 · 27 files |
NodeDefinition 패턴 도입
typescript
// stores/nodes/text/definition.ts (요지)
export const textNodeDefinition = {
type: 'text',
tool: 'text',
shortcut: 't',
createState,
createFabricObject,
stateFromFabricObject,
applyStateToFabricObject,
};| 영역 | 주요 추가 |
|---|---|
stores/nodes/ | registry.ts, base.ts, types.ts, text index.ts |
drawing/placement.ts | 클릭·드래그 배치 (260줄) |
hooks/ | useDrawingTools, useCanvasSelection (104줄) |
| UI | propertiesSidebar/, shadcn sidebar.tsx (640줄), sheet, tooltip |
| Store | editorSlice, selection·command 연동 |
UX
- 단축키
T→ 텍스트 도구 → 캔버스 클릭 배치 - 우측 Properties Sidebar에서 fontSize, color, fill 편집
→ 노드 추가하기
#33 Backspace 삭제
stores/commands/index.ts에selection.deleteSelectioncommand 8줄- shortcut:
delete/backspace— 선택된selectedIds일괄removeNode
2026-05-20 — 캔버스 에디터 핵심 기능
| 지표 | 3 PR · +1,745 / −38 |
#22 캔버스 초기화
| 지표 | +1,138 / −21 · 17 files |
| 추가 | 설명 |
|---|---|
fabric 의존성 | Canvas 2D 렌더링 엔진 |
routes/canvas/ | /canvas 전용 레이아웃·페이지 |
components/canvas/index.tsx | Fabric 래퍼 + ref |
useFabricCanvas.ts | mount/dispose, resize observer |
themeProvider.tsx, theme-mode-toggle | light/dark/system |
dev/registry.ts, global-injection.ts | 개발 중 store/canvas 디버그 노출 |
shadcn dropdown-menu | 향후 메뉴·도구 UI 기반 |
#23 Command & Shortcut
| 지표 | +364 / −11 · 14 files |
Command 스토어 구조
stores/commands/
├── index.ts # COMMANDS[], executeCommand, useAppStore
├── types.ts # Command, AppState
└── slices/
├── canvasSlice.ts
├── historySlice.ts # undo/redo 스택 (도구 변경 추적)
└── selectionSlice.ts| Command 그룹 | 예시 ID | 단축키 |
|---|---|---|
| tools | tool.move, tool.text | v, t |
| history | history.undo, history.redo | mod+z, mod+shift+z |
| selection | selection.selectAll, selection.deleteSelection | mod+a, delete |
useGlobalShortcuts.ts—keyCombo.ts로mod/shift정규화- 확장 포인트:
NODE_DEFINITIONS를 map해 도구 command 자동 등록
#27 카메라 (줌·팬)
| 지표 | +243 / −6 |
| 파일 | 역할 |
|---|---|
zoomAtPoint.ts | 커서 앵커 기준 줌 — Figma/Excalidraw 패턴 |
useCanvasCamera.ts | Fabric viewport ↔ store zoom/position |
zoomLevel.tsx, position.tsx | HUD UI |
canvasSlice.ts | setViewport, panBy, resetViewport |
2026-05-19 — 앱 라우팅 · 협업 규칙
| 지표 | 3 PR · +400 / −35 |
#21 TanStack Router
- Next.js 잔재 제거 후 Vite SPA + 파일 기반 라우팅
routes/__root.tsx,routes/index.tsx,routeTree.gen.ts(codegen)tsr.config.json, Vite 플러그인 연동
#19 PR 템플릿
- 이슈 번호
close #N, Description, as-is / to-be 테스트 증적 섹션
#17 Docker 안정화
- Dockerfile.dev에 pnpm 버전 핀
- README 중복 제거, 실행 명령 단일화
2026-05-18 — 프로젝트 초기 설정 · 개발 환경
| 지표 | 5 커밋 · +15,625 / −3,060 (pnpm-lock·스캐폴딩 포함) |
wip + init — Turborepo 모노레포 골격
| 커밋 | Δ | 내용 |
|---|---|---|
434c294 wip | +9,490 · 84 files | turbo, eslint-config, jest-config, packages/api DTO, Nest links 샘플, Next.js web |
0df241f init | +4,244 / −1,019 · 43 files | Next → Vite 전환, packages/ui 제거, shadcn button, vite.json tsconfig |
최종 앱 구조
apps/api— NestJS 11apps/web— React + Vite (포트 3001)packages/api— 공유 DTO (@repo/api)
레이블 워크플로 (커밋 9e1e928)
.github/workflows/label.yml—workflow_dispatch로 레이블 일괄 생성- Priority High/Medium/Low, Type: Feature/Bug/Doc/Refactor/UX 등 9종
#15 Docker Compose
yaml
# docker-compose.dev.yml (요지)
services:
web: # :3001, hot reload volumes
api: # :3000, DATABASE_URL, OAuth env
db: # PostgreSQL 17, healthcheck| 산출물 | 설명 |
|---|---|
docker-compose.yml / .dev.yml | prod vs dev 오버레이 |
apps/*/Dockerfile.dev | monorepo context build |
package.json scripts | docker:dev, docker:down 등 |
Favicon (f813152)
apps/web/public/Favicon.svg— 브랜드 아이덴티티
알려진 제한 사항 · 향후 작업
| 항목 | 현재 상태 | 메모 |
|---|---|---|
| 캔버스 ↔ API 동기화 | 구현됨 | 문서 전체 PUT debounce. 노드 단위 PATCH·실시간 협업은 향후 |
| 워크스페이스 | 구현됨 | 목록·생성·/canvas/:id, 멤버십 기반 접근 제어 |
| undo/redo | history 스택 존재 | 노드 편집 히스토리는 제한적 |
| 뷰포트 persist | 미저장 | #42에서 의도적 제외 |
| E2E 테스트 | Jest 단위 위주 | 캔버스 시나리오 E2E는 확장 여지 |
권장 읽기 순서
- 프로젝트 개요 — 목적·기술 스택
- 2026-05-20 ~ 05-22 — 에디터 관련 PR (#22–#43)
- 2026-05-27 — 문서 사이트·CI (#45)
- 2026-06-01 — 인증·품질 도구 (#49, #51)
- 2026-06-01 (이후) — 워크스페이스 · 노드 서버 저장
- 아키텍처 개요 — 설계 원칙