diff --git a/.cursor/agents/architect.md b/.cursor/agents/architect.md
new file mode 100644
index 00000000..87c5afa3
--- /dev/null
+++ b/.cursor/agents/architect.md
@@ -0,0 +1,32 @@
+# Agent Role: Architect (Sion)
+
+# Goal
+요구사항을 분석하여 **기능 명세서(spec/stories)** 를 작성하는 역할.
+이 명세는 QA가 테스트 설계를, Dev가 코드를 작성하는 기준이 된다.
+
+# Responsibilities
+- 기술적 범위와 제약 정의(비즈니스 AC는 PM이 소유)
+- 도메인 용어/제약 정리
+- 입력/출력 계약 설계(API, 이벤트, 상태)
+- 데이터 모델과 스키마 초안
+- 상태/흐름/시퀀스 다이어그램(텍스트 기반)
+- 에러/복구/롤백 전략
+- 성능/보안/접근성/테스트 용이성 등 NFR 정의
+- 오픈 이슈와 리스크 관리
+
+# Deliverables
+- 기능 명세서 1개(본 파일의 템플릿 준수)
+- 입력/출력/예외 표
+- API 계약(요청/응답/에러 코드)
+- 데이터 모델(엔티티/필드/제약)
+- 흐름 다이어그램(텍스트)
+- 오픈 이슈 목록과 결론
+
+# Input, Output
+| 구분 | 형식 | 경로 |
+|------|------|------|
+| **입력(Input)** | 요구사항 문서 | `spec/requirements/*.md` |
+| **출력(Output)** | 기능 명세서 | `spec/stories/{feature}.md` |
+
+# Reference
+- .cursor/docs/architect-reference.md
diff --git a/.cursor/agents/ceo.md b/.cursor/agents/ceo.md
new file mode 100644
index 00000000..3d995dbc
--- /dev/null
+++ b/.cursor/agents/ceo.md
@@ -0,0 +1,71 @@
+# Agent Role: CEO (Riku)
+
+# Goal
+당신은 이 프로젝트의 **총괄 Orchestrator (CEO)**입니다.
+프로젝트의 모든 사이클을 관리하며,
+아래 단계에 따라 자동으로 **TDD 기반 개발 사이클**을 수행하고 승인합니다.
+
+## 🧭 사이클 개요
+
+| 단계 | 담당 에이전트 | 설명 | 결과물 |
+|------|----------------|------|---------|
+| 1️⃣ Spec | Architect | 기능 설계 및 명세 문서 작성 | `spec/stories/*.md` |
+| 2️⃣ Red | QA | 테스트 설계 및 실패 테스트 작성 | `spec/tests/*.md` |
+| 3️⃣ Green | Dev | 테스트를 통과시키는 최소 코드 작성 | `src/**/*.ts` + `__tests__/*.spec.ts` |
+| 4️⃣ Refactor | Dev | 코드 정리 및 중복 제거 | `src/**/*.ts` |
+| 5️⃣ Verify | QA | 테스트 실행 및 검증 리포트 작성 | `outputs/validation/*.md` |
+| 6️⃣ Approve | CEO | 전체 산출물 검토 및 승인 보고서 작성 | `outputs/reports/*.md` |
+
+---
+
+## ⚙️ 실행 규칙 (Workflow Definition)
+
+### 🔹 Step 1. 기능 명세 (Architect)
+- `spec/requirements/*.md` 또는 사용자가 지정한 요구사항을 기반으로
+- `Architect Agent`에게 다음 명령을 전달:
+ > “요구사항을 기반으로 기능 명세서를 작성하라.”
+- 출력 경로: `spec/stories/{feature}.md`
+
+---
+
+### 🔹 Step 2. 테스트 설계 (QA) — RED
+- Architect가 작성한 기능 명세서를 읽고
+- `QA Agent`에게 다음 명령을 전달:
+ > “기능 명세에 따라 실패하는 테스트를 설계하고 테스트 문서를 작성하라.”
+- 출력 경로: `spec/tests/{feature}-test-design.md`
+
+---
+
+### 🔹 Step 3. 코드 작성 (Dev) — GREEN
+- QA가 만든 테스트 설계서를 기반으로
+- `Dev Agent`에게 다음 명령을 전달:
+ > “테스트 설계서 기반으로 테스트를 통과시키는 최소한의 코드를 작성하라.”
+- 출력: `src/{feature}.ts`, `__tests__/{feature}.spec.ts`
+
+---
+
+### 🔹 Step 4. 리팩토링 (Dev) — REFACTOR
+- Dev에게 다시 명령:
+ > “방금 작성한 코드를 리팩토링하라. 중복을 제거하고 구조를 개선하라.”
+- 출력: 수정된 `src/{feature}.ts`
+
+---
+
+### 🔹 Step 5. 검증 (QA) — VERIFY
+- QA에게 명령:
+ > “리팩토링된 코드를 테스트하고, 결과를 리포트로 남겨라.”
+- 출력: `outputs/validation/{feature}-qa-report.md`
+
+---
+
+### 🔹 Step 6. 승인 (CEO) — APPROVE
+- CEO 스스로 검증 리포트를 읽고 승인 여부 결정:
+ > “테스트 결과와 명세 일치 여부를 검토한 뒤 승인 보고서를 작성하라.”
+- 출력: `outputs/reports/{feature}-cycle-summary.md`
+
+
+# Reference
+# - 역할 분담은 BMAD-METHOD의 에이전트 분업 철학을 참조
+
+- https://github.com/bmad-code-org/BMAD-METHOD/
+- .cursor/docs/tdd-document.md
diff --git a/.cursor/agents/dev.md b/.cursor/agents/dev.md
new file mode 100644
index 00000000..4c1df370
--- /dev/null
+++ b/.cursor/agents/dev.md
@@ -0,0 +1,49 @@
+# Agent Role: Developer (Sakuya)
+
+# Goal
+QA의 테스트 설계서를 기반으로 코드를 구현하고,
+테스트를 통과시키는 **GREEN** 단계 및 **REFACTOR** 단계를 담당한다.
+
+# Responsibilities
+- 기능 구현(React TS 구조, 훅/유틸 분리, 접근성/성능 준수)
+- 단위/컴포넌트 테스트 작성, 린트/타입 오류 해결, 빌드 통과
+- 변경내역 문서화(PR 설명, 중요한 결정 기록)
+- 기술적 제약/리스크를 Architect/PM에 조기 알림
+- 품질 이슈 수정 및 회귀 방지
+- 최소한의 코드로 먼저 테스트 통과 후 리팩토링
+- 결과물을 CEO 승인 전 QA 검증 후 승인
+
+# Deliverables
+- 코드 변경(기능/테스트 포함)
+- PR 설명(요약/범위/테스트/리스크/추가 작업)
+- 변경 로그(필요 시)
+
+# Input, Output
+| 구분 | 형식 | 경로 |
+|------|------|------|
+| **입력(Input)** | 테스트 설계서 | `spec/tests/{feature}-test-design.md` |
+| **출력(Output)** | 기능 코드 | `src/{feature}.ts` |
+| **출력(Output2)** | 테스트 코드 | `src/__tests__/{feature}.spec.ts` |
+
+# Interfaces
+- To QA: 빌드 아티팩트/체인지로그 제공, 결함 수정 및 확인
+- To PM: 스토리 진행/블로커 보고
+- To Architect: 설계 이슈/트레이드오프 질의
+
+# Non-Goals
+- PRD/AC 작성(PM 영역)
+- 아키텍처/솔루션 결정(Architect 영역)
+- 테스트 전략/품질 게이트 정의(QA 영역)
+
+# Reference
+# - 역할 분담은 BMAD-METHOD의 개발 에이전트 분업을 참조
+# - 테스트 코드 개발 시 kent beck testing 참고
+
+- https://github.com/bmad-code-org/BMAD-METHOD/
+- .cursor/docs/tdd-document.md
+- .cursor/docs/kent-beck-testing.md
+
+## 최소 변경/영향 최소화 원칙
+- 기존 공개 API/타입/파일 경로 유지, 내부 로직만 국소 변경
+- 기존 테스트와 호환을 유지하고, 신규 규칙을 커버하는 테스트만 추가
+- 관심사 분리 준수: 유틸 변경으로 해결하고 컴포넌트/훅은 변경 최소화
\ No newline at end of file
diff --git a/.cursor/agents/qa.md b/.cursor/agents/qa.md
new file mode 100644
index 00000000..d27c32c4
--- /dev/null
+++ b/.cursor/agents/qa.md
@@ -0,0 +1,43 @@
+# Agent Role: QA Engineer (Yushi)
+
+# Goal
+Architect의 기능 명세를 기반으로 테스트 설계를 수행한다.
+TDD 사이클의 **RED** (실패 테스트)와 **VERIFY** (검증) 단계를 담당한다.
+
+# Responsibilities
+- 테스트 전략/계획 수립(범위, 기법, 환경, 데이터, 게이트)
+- AC 기반 테스트 케이스 설계, NFR 기반 비기능 테스트(성능/접근성/보안) 계획
+- 테스트 환경/데이터 관리, 실행 및 자동화 파이프라인 연계
+- 결함 리포팅(재현 절차/로그/우선순위) 및 트라이애지 주도
+- 품질 게이트 정의 및 릴리스 사전/사후 검증, 서명
+
+# Deliverables
+- Test Plan(전략/범위/환경/게이트)
+- Test Case Matrix(스토리/AC 매핑, 엣지/회귀 포함)
+- Test Execution Report 및 Release Sign-off
+
+# Input, Output
+| 구분 | 형식 | 경로 |
+|------|------|------|
+| **입력(Input)** | 기능 명세서 | `spec/stories/{feature}.md` |
+| **출력(Output)** | 검증 리포트 | `outputs/{feature}-qa-report.md` |
+
+# Interfaces
+- To Dev: 결함 리포트/재현 절차/우선순위 전달, 수정 확인
+- To PM: 품질 상태/AC 충족 여부 보고, 출시 리스크 공유
+- To CEO: 릴리스 권고 요약
+- From Architect: 테스트 가능성 관련 제약/리스크 인수
+
+# Reference
+# - 역할 분담은 BMAD-METHOD의 QA/품질 게이트 철학을 참조
+# - 테스트 코드 설계 시 kent beck testing 참고
+
+- .cursor/docs/testing-library-queries-priority.md
+- https://github.com/bmad-code-org/BMAD-METHOD/
+- .cursor/docs/kent-beck-testing.md
+
+## 최소 변경/영향 최소화 테스트 전략
+- 구현 변경을 강제하지 않는 테스트 작성(기존 공개 API/시그니처 유지)
+- 새로운 규칙을 드러내는 케이스만 추가하고, 기존 케이스와 충돌하지 않도록 구성
+- 겹침(overlap) 미검증 정책을 전제(겹침 관련 단언 금지)
+- .cursor/docs/tdd-document.md
diff --git a/.cursor/checklist/ceo-approval-checklist.md b/.cursor/checklist/ceo-approval-checklist.md
new file mode 100644
index 00000000..7fff0198
--- /dev/null
+++ b/.cursor/checklist/ceo-approval-checklist.md
@@ -0,0 +1,60 @@
+# 🧾 CEO 승인 체크리스트
+
+> Purpose: 각 에이전트의 1차 자체 점검 결과를 종합 검토하여 최종 승인/보류/반려를 결정합니다.
+
+---
+
+## 1️⃣ 산출물 수령 확인
+| 에이전트 | 산출물 | 링크/경로 | 자체 점검 결과 | 확인 |
+|---------|--------|-----------|----------------|------|
+| 테스트설계 | 테스트 시나리오 명세 · 체크리스트 | `.cursor/outputs/test-design-review.md` | ✅/⚠️ | ☐ |
+| 테스트코드 | 테스트 코드 · 커버리지 | `.cursor/outputs/test-code-review.md` | ✅/⚠️ | ☐ |
+| 코드작성 | 변경 코드 · 타입/상수 | `.cursor/outputs/code-implementation-review.md` | ✅/⚠️ | ☐ |
+| 리팩토링 | 변경 전/후 요약 · 회귀 목록 | `.cursor/outputs/refactoring-review.md` | ✅/⚠️ | ☐ |
+| 오케스트레이션 | 파이프라인 로그 · 링크 모음 | `.cursor/outputs/orchestration-review.md` | ✅/⚠️ | ☐ |
+
+---
+
+## 2️⃣ 품질 게이트
+| 항목 | 기준 | 증빙 | 통과 |
+|------|------|------|------|
+| 린트 | `pnpm lint` 경고/에러 0 | 로그 | ☐ |
+| 테스트 | `pnpm test` 전부 통과, 커버리지 목표 | 리포트 | ☐ |
+| 빌드 | `pnpm build` 성공 | 로그 | ☐ |
+
+---
+
+## 3️⃣ 도메인·규칙 준수
+| 항목 | 기준 | 통과 |
+|------|------|------|
+| 날짜/시간 | ISO/UTC/24시간, 윤년/월경계 | ☐ |
+| 반복 일정 | 종료일 제한, 31일/2월29일, 단일/전체 수정/삭제 | ☐ |
+| 접근성 | id/aria-label/시맨틱, Material-UI 패턴 | ☐ |
+| 타입 | any 금지, 명시적 타입, JSDoc | ☐ |
+| 아키텍처 | 훅/유틸/컴포넌트 경계 | ☐ |
+
+---
+
+## 4️⃣ 리스크 및 의사결정
+| 리스크 | 영향 | 대응 | 상태 |
+|--------|------|------|------|
+| | | | |
+
+---
+
+## 5️⃣ 최종 결정
+- 승인/보류/반려:
+- 비고:
+- 승인자: CEO
+- 일자:
+
+---
+
+## 📚 참고 문서
+- `.cursor/docs/tdd-document.md`
+- `.cursorrules`
+
+## 🧾 제출 지침
+- 최종 승인 결과는 `.cursor/outputs/ceo-approval.md`에 기록합니다.
+
+
diff --git a/.cursor/checklist/code-implementation-agent-checklist.md b/.cursor/checklist/code-implementation-agent-checklist.md
new file mode 100644
index 00000000..b5af70a8
--- /dev/null
+++ b/.cursor/checklist/code-implementation-agent-checklist.md
@@ -0,0 +1,95 @@
+# 🧩 코드작성 에이전트 검증 체크리스트
+
+> Purpose: 테스트 주도 구현 시, 아키텍처·타입·접근성·성능·도메인 규칙을 준수했는지 검증합니다. (1차 자체 점검 → CEO 승인)
+
+---
+
+## 1️⃣ 요구사항 명확성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | TDD 흐름 | 실패 테스트 → 최소 구현 → 리팩토링 순서 준수 | ☐ |
+| 1-2 | 도메인 적합 | 캘린더 용어/규칙(ISO/UTC/24시간/윤년/월경계) 반영 | ☐ |
+
+---
+
+## 2️⃣ 입력·출력 정의
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 명시적 타입 | 인터페이스/유니온/반환 타입 명시, any 금지 | ☐ |
+| 2-2 | API I/O | 오류 처리/로딩 상태/사용자 피드백 일관 | ☐ |
+
+---
+
+## 3️⃣ 처리 로직 구체성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | 관심사 분리 | 훅=로직, 유틸=순수함수, 컴포넌트=UI | ☐ |
+| 3-2 | 훅 패턴 | 객체 반환, 로딩/에러 상태, useCallback/useMemo 사용 | ☐ |
+| 3-3 | 유틸 품질 | 순수성, 명명된 상수, 50줄 이하, 중복 제거 | ☐ |
+| 3-4 | 반복 일정 | 종료일 제한, 31일/2월29일, 그룹 수정/삭제 처리 | ☐ |
+| 3-5 | 겹침/시간 검증 | 시작<종료, 겹침 탐지, 잘못된 조합 금지 | ☐ |
+
+---
+
+## 4️⃣ 에러 및 예외 처리
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 사용자 피드백 | 폼 에러 헬퍼텍스트/다이얼로그, 접근성 속성 제공 | ☐ |
+| 4-2 | 로깅 | 개발 단계 콘솔/에러 로깅 기준 일관 | ☐ |
+
+---
+
+## 5️⃣ 테스트 기반성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | 테스트 통과 | 신규/기존 테스트 모두 통과 | ☐ |
+| 5-2 | 커버리지 영향 | 의미 있는 라인 증가, 미달 사유 기록 | ☐ |
+
+---
+
+## 6️⃣ 문서 품질·코드 스타일
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 6-1 | JSDoc | 복잡 함수에 JSDoc, 반환/파라미터 타입 기술 | ☐ |
+| 6-2 | ESLint/TS | 경고/에러 0, 엄격 TS 규칙 준수 | ☐ |
+| 6-3 | Import 순서 | 외부 → 내부(hooks/utils/types) 순서 | ☐ |
+
+---
+
+## 7️⃣ 성능·접근성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 7-1 | 렌더 최적화 | 불필요 리렌더 방지, 핸들러 useCallback | ☐ |
+| 7-2 | 접근성 | id/aria-label/시맨틱 요소, Material-UI 패턴 준수 | ☐ |
+
+---
+
+## 8️⃣ 프롬프트 품질
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 8-1 | 산출물 명시 | 변경 파일, 타입 정의, 훅/유틸 경계 설명 | ☐ |
+| 8-2 | 금지사항 | 컴포넌트에 비즈니스 로직, any, 매직 넘버 금지 | ☐ |
+
+---
+
+## 📎 산출물
+- 변경된 소스 목록과 요약(아키텍처 영향 포함)
+- 타입/상수 추가 목록
+- 사용자 피드백(에러/로딩) 확인 캡처 또는 설명
+
+---
+
+## 📚 참고 문서
+- `.cursor/docs/tdd-document.md` — TDD 사이클과 구조/행동 커밋 분리
+- `.cursorrules` — 훅/유틸/컴포넌트 경계·접근성·타입·도메인 규칙
+
+## 🧾 제출 지침
+- 본 체크리스트로 1차 자체 점검 완료 후 결과 보고서를 다음 경로에 작성:
+ - `.cursor/outputs/code-implementation-review.md`
+- 보고서에는 다음을 포함:
+ - 변경 파일 목록과 영향 범위
+ - 규칙 준수 근거(코드 스니펫/라인 참조)
+ - 명령/로그: `pnpm lint`, `pnpm build`
+
+
+
diff --git a/.cursor/checklist/feature-verification.md b/.cursor/checklist/feature-verification.md
new file mode 100644
index 00000000..9eb83b5b
--- /dev/null
+++ b/.cursor/checklist/feature-verification.md
@@ -0,0 +1,92 @@
+# 🧩 기능설계 에이전트 검증 템플릿
+
+> **Purpose**
+> This template verifies whether the output from the **Feature Design Agent (기능설계 에이전트)**
+> meets TDD-friendly standards of clarity, testability, and system consistency.
+
+---
+
+## 1️⃣ 요구사항 명확성 (Requirement Clarity)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | 요구사항이 명시적으로 표현되어 있는가 | “무엇을 해야 하는가”가 구체적으로 기술되어 있는가 | ☐ |
+| 1-2 | 비기능 요구사항이 포함되어 있는가 | 예: 보안, 성능, 오류 처리 등 시스템 제약사항 포함 여부 | ☐ |
+| 1-3 | 테스트 가능한 형태로 변환 가능한가 | 결과가 “확인 가능한 조건”으로 서술되어 있는가 | ☐ |
+
+---
+
+## 2️⃣ 입력·출력 정의 (I/O Specification)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 입력값이 명시되어 있는가 | 파라미터명, 타입, 필수 여부가 정의되어 있는가 | ☐ |
+| 2-2 | 출력값이 명시되어 있는가 | 반환 데이터 구조나 응답 형식이 정의되어 있는가 | ☐ |
+| 2-3 | 입력과 출력이 논리적으로 일관적인가 | 입력 조건 변화에 따른 출력이 일관성 있는가 | ☐ |
+| 2-4 | 데이터 타입 정의가 명확한가 | string/number/boolean 등 타입이 누락되지 않았는가 | ☐ |
+
+---
+
+## 3️⃣ 처리 로직 구체성 (Process Logic)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | 처리 단계가 순서대로 정의되어 있는가 | 입력 검증 → 처리 → 결과 반환 등 단계적 구조인가 | ☐ |
+| 3-2 | 각 단계가 테스트 가능하게 서술되어 있는가 | 결과를 검증할 수 있을 정도로 조건과 결과가 명확한가 | ☐ |
+| 3-3 | 조건 분기(예외 흐름)가 포함되어 있는가 | 정상 흐름 외에도 실패·예외 케이스가 포함되어 있는가 | ☐ |
+
+---
+
+## 4️⃣ 에러 및 예외 처리 (Error Handling)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 주요 실패 케이스가 정의되어 있는가 | 입력 누락, 검증 실패, 비즈니스 예외 등 | ☐ |
+| 4-2 | 에러 응답 형식이 일관된가 | 코드·메시지 구조가 명확하고 통일되어 있는가 | ☐ |
+| 4-3 | 성공/실패 응답 구분이 명확한가 | 응답 필드가 중복되거나 모호하지 않은가 | ☐ |
+
+---
+
+## 5️⃣ 테스트 기반성 (Testability)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | 기능 단위가 작고 독립적인가 | 다른 기능과 결합 없이 테스트 가능한가 | ☐ |
+| 5-2 | 각 입력에 대한 기대 결과가 명확한가 | 예상 결과가 단일하고 구체적인가 | ☐ |
+| 5-3 | 명세에서 테스트 시나리오를 도출할 수 있는가 | “Given-When-Then” 구조로 변환 가능성이 있는가 | ☐ |
+
+---
+
+## 6️⃣ 명세 문서 품질 (Documentation Quality)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 6-1 | 섹션 구조가 일관성 있는가 | Feature / Input / Process / Output / Error 순서 유지 | ☐ |
+| 6-2 | 표(Table) 형식이 깨지지 않았는가 | 마크다운 표가 정상적으로 렌더링되는가 | ☐ |
+| 6-3 | 중복·모순된 내용이 없는가 | 동일 내용이 반복되거나 충돌되지 않는가 | ☐ |
+| 6-4 | 요약이 명확한가 | 첫 문단에서 이 기능의 목적이 드러나는가 | ☐ |
+
+---
+
+## 7️⃣ 프로젝트 맥락 및 영향 분석 (Context & Impact)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 7-1 | 도메인 용어가 일관된가 | 일정, 이벤트, 날짜, 사용자 등 프로젝트 용어에 맞는가 | ☐ |
+| 7-2 | 기존 기능과 충돌하지 않는가 | 기존 기능(조회, 수정, 삭제 등)과 로직적으로 겹치지 않는가 | ☐ |
+| 7-3 | 새로운 기능의 경계가 명확한가 | 기존 기능과의 입력·출력, 책임 구분이 명확한가 | ☐ |
+| 7-4 | 기존 기능의 동작에 영향이 없는가 | 기존 API, DB 스키마, UI 로직이 깨질 위험이 없는가 | ☐ |
+| 7-5 | 영향 범위가 정의되어 있는가 | 수정된 모듈이 어디에 영향을 미치는지 명시되어 있는가 | ☐ |
+| 7-6 | 회귀 테스트 필요성이 식별되었는가 | 기존 기능 중 재검증이 필요한 부분이 언급되었는가 | ☐ |
+
+---
+
+## 8️⃣ 에이전트 품질 점검 (Prompt Quality)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 8-1 | 에이전트 프롬프트가 일관된 형식을 요구하는가 | Feature / Input / Process / Output / Error 구조 유지 | ☐ |
+| 8-2 | 언어·포맷 지시가 명확한가 | “표로 작성”, “마크다운 형식” 등 출력 형식 명시 | ☐ |
+| 8-3 | 일반적 서술을 제한했는가 | 추상적 지시(“간단히 설명”)가 없는가 | ☐ |
+| 8-4 | Rules(.cursorrules)와 일관되는가 | 언어, 테스트 프레임워크, 코드 스타일이 규칙과 맞는가 | ☐ |
+
+---
+
+## 🧾 제출 지침
+- 본 체크리스트로 1차 자체 점검 완료 후 결과 보고서를 다음 경로에 작성:
+ - `.cursor/outputs/feature-verification-review.md`
+- 보고서에는 다음을 포함:
+ - 각 항목별 점검 결과 및 피드백
+ - 최종 검증 결과 요약
diff --git a/.cursor/checklist/orchestration-agent-checklist.md b/.cursor/checklist/orchestration-agent-checklist.md
new file mode 100644
index 00000000..3bbc2b1b
--- /dev/null
+++ b/.cursor/checklist/orchestration-agent-checklist.md
@@ -0,0 +1,66 @@
+# 🧵 오케스트레이션 에이전트 검증 체크리스트
+
+> Purpose: 테스트설계 → 테스트코드 → 코드작성 → 리팩토링의 순서를 관리하고, 승인 게이트와 산출물 연결성을 검증합니다. (1차 자체 점검 → CEO 승인)
+
+---
+
+## 1️⃣ 흐름/게이트 설계
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | 단계 정의 | 4단계 흐름과 각 단계 산출물/입력/출력 정의 | ☐ |
+| 1-2 | 승인 게이트 | 각 단계별 자체 체크리스트 ✅ 후에만 다음 단계 진행 | ☐ |
+| 1-3 | 롤백 계획 | 실패 시 이전 안정 지점으로 복귀 절차 | ☐ |
+
+---
+
+## 2️⃣ 입력·출력 연결성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 산출물 참조 | 다음 단계가 이전 단계 산출물을 정확히 참조 | ☐ |
+| 2-2 | 추적성 | 시나리오 ↔ 테스트 ↔ 코드 변경 추적 가능 | ☐ |
+
+---
+
+## 3️⃣ 품질 게이트 실행
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | 명령어 파이프라인 | `pnpm lint` → `pnpm test` → `pnpm build` 자동/수동 실행 | ☐ |
+| 3-2 | 보고 | 실패 원인/영향/대응계획을 간결히 리포팅 | ☐ |
+
+---
+
+## 4️⃣ 리스크/의사결정 관리
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 리스크 레지스터 | 성능/접근성/도메인 규칙 위반 리스크 추적 | ☐ |
+| 4-2 | 의사결정 기록 | 대안/선택 근거, 승인자, 날짜 기록 | ☐ |
+
+---
+
+## 5️⃣ 커뮤니케이션/문서화
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | 변경 로그 | 주요 변경과 영향 범위 요약 | ☐ |
+| 5-2 | PR 템플릿 | 체크리스트 링크/증빙 첨부, 리스크·테스트 증거 포함 | ☐ |
+
+---
+
+## 📎 산출물
+- 단계별 체크리스트 링크 모음
+- 빌드/테스트 로그 요약
+- 리스크·의사결정 기록
+
+---
+
+## 📚 참고 문서
+- `.cursor/docs/tdd-document.md` — 단계별 게이트와 커밋 분리 원칙 반영
+- `.cursorrules` — 품질 게이트 명령/도메인 규칙 준수 확인
+
+## 🧾 제출 지침
+- 본 체크리스트로 1차 자체 점검 완료 후 결과 보고서를 다음 경로에 작성:
+ - `.cursor/outputs/orchestration-review.md`
+- 보고서에는 다음을 포함:
+ - 단계별 산출물 경로 체크 및 추적성 표
+ - 품질 게이트 실행 로그 집계
+
+
diff --git a/.cursor/checklist/refactoring-agent-checklist.md b/.cursor/checklist/refactoring-agent-checklist.md
new file mode 100644
index 00000000..2d8d00ce
--- /dev/null
+++ b/.cursor/checklist/refactoring-agent-checklist.md
@@ -0,0 +1,75 @@
+# 🔧 리팩토링 에이전트 검증 체크리스트
+
+> Purpose: 외부 동작을 유지하면서 가독성·성능·구조를 개선했는지, 테스트 안정성을 해치지 않았는지 검증합니다. (1차 자체 점검 → CEO 승인)
+
+---
+
+## 1️⃣ 요구사항 명확성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | 리팩토링 범위 | 대상 파일/모듈과 비대상 명확화(스코프 크립 방지) | ☐ |
+| 1-2 | 목표 정의 | 중복 제거, 명명 개선, 구조화, 성능 개선 등 목적 명시 | ☐ |
+
+---
+
+## 2️⃣ 입력·출력/행동 동일성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 퍼블릭 API 불변 | 함수 시그니처/반환 타입/훅 반환 객체 불변 | ☐ |
+| 2-2 | 테스트 무변경 통과 | 기존 테스트 수정 없이 통과(필요 시 합리적 근거) | ☐ |
+
+---
+
+## 3️⃣ 처리 로직 개선
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | 분해/추출 | 50줄 이상 함수 분해, 유틸로 추출, 이름 개선 | ☐ |
+| 3-2 | 성능 | 불필요 연산/렌더 제거, useMemo/useCallback 적용 | ☐ |
+| 3-3 | 타입 강화 | any 제거, 유니온/인터페이스 명확화 | ☐ |
+
+---
+
+## 4️⃣ 에러/예외·회귀 안전망
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 회귀 테스트 | 영향 영역 테스트 추가/보강 | ☐ |
+| 4-2 | 로깅·에러 | 기존 에러 처리 흐름 유지, 메시지 일관 | ☐ |
+
+---
+
+## 5️⃣ 문서·스타일
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | JSDoc/주석 | 비자명한 의도/제약 조건만 간결히 문서화 | ☐ |
+| 5-2 | ESLint/TS | 경고 0, 엄격 TS, import 순서 준수 | ☐ |
+
+---
+
+## 6️⃣ 맥락·영향
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 6-1 | 경계 준수 | 훅/유틸/컴포넌트 경계 유지, 의존 역전 지키기 | ☐ |
+| 6-2 | 변경 영향 | 영향 범위/리스크/롤백 계획 기록 | ☐ |
+
+---
+
+## 📎 산출물
+- 변경 전/후 요약(목적-효과-영향)
+- 영향 범위 회귀 테스트 목록
+- 퍼블릭 API 변경 없음 확인 근거
+
+---
+
+## 📚 참고 문서
+- `.cursor/docs/tdd-document.md` — Green 상태에서만 리팩토링, Tidy First
+- `.cursorrules` — 50줄 제한/중복 제거/명명 규칙/성능·접근성 규칙
+
+## 🧾 제출 지침
+- 본 체크리스트로 1차 자체 점검 완료 후 결과 보고서를 다음 경로에 작성:
+ - `.cursor/outputs/refactoring-review.md`
+- 보고서에는 다음을 포함:
+ - 전/후 비교와 각 리팩토링의 의도·효과
+ - 회귀 안전성 증빙(테스트 통과 로그)
+
+
+
diff --git a/.cursor/checklist/test-code-agent-checklist.md b/.cursor/checklist/test-code-agent-checklist.md
new file mode 100644
index 00000000..4bbd5a88
--- /dev/null
+++ b/.cursor/checklist/test-code-agent-checklist.md
@@ -0,0 +1,123 @@
+# 🧪 테스트코드 작성 에이전트 검증 체크리스트
+
+> Purpose: 테스트 설계 명세를 실행 가능한 테스트로 구현할 때, 타입 안전성·접근성·결정성을 보장하고 프로젝트 규칙을 준수했는지 검증합니다. (1차 자체 점검 → CEO 승인)
+
+---
+
+## 1️⃣ 요구사항 명확성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | 시나리오 매핑 | 모든 시나리오가 테스트 파일/케이스로 일대일 매핑 | ☐ |
+| 1-2 | 명명 규칙 | 테스트 이름이 한글 설명, 시나리오 서술형 | ☐ |
+| 1-3 | 커버리지 목표 | 파일/라인/브랜치 목표와 미달 사유 기록 | ☐ |
+
+---
+
+## 2️⃣ 입력·출력 정의
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 고정된 테스트 데이터 | 타임존 UTC, 시드 고정, ISO/24시간 형식 사용 | ☐ |
+| 2-2 | MSW 핸들러 | `src/__mocks__/handlers.ts` 활용, API 경계 모킹 | ☐ |
+| 2-3 | 관찰 포인트 | 화면 텍스트/role/aria, 훅 상태, 유틸 반환값 검증 | ☐ |
+
+---
+
+## 3️⃣ 처리 로직 구체성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | AAA 패턴 | Arrange-Act-Assert 구조 준수 | ☐ |
+| 3-2 | 구현 세부 회피 | 내부 구현이 아닌 공개 API/사용자 관점 검증 | ☐ |
+| 3-3 | 동기/비동기 처리 | `await`/`findBy*`/`waitFor` 적절 사용 | ☐ |
+| 3-4 | 훅 테스트 | `renderHook`, `act` 사용, 상태·에러·로딩 검증 | ☐ |
+
+---
+
+## ♾️ 전역 엣지 케이스 커버리지 게이트 (Global Edge-Case Coverage Gate)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| EC-1 | 경계값 테스트 | min/max/빈 값/잘못된 형식/오버플로우 케이스 구현 | ☐ |
+| EC-2 | 달력 도메인 경계 | 월 경계, 윤년 2/29, 주 계산, 타임존 고정 테스트 | ☐ |
+| EC-3 | 에러 플로우 | API 4xx/5xx/네트워크 실패, 사용자 피드백 단언 | ☐ |
+| EC-4 | 접근성 실패 | id/aria 누락 시 스크린리더 관찰 포인트 검증 | ☐ |
+| EC-5 | 성능 상한 | 최대 데이터셋에서 렌더/검색 시간 과도하지 않음 | ☐ |
+| EC-6 | 트레이스 매핑 | 요구사항 ↔ 테스트케이스 표를 리포트에 포함 | ☐ |
+| EC-7 | 결정성 | `vi.setSystemTime`/`useFakeTimers` 등으로 결정성 보장 | ☐ |
+
+---
+
+## 4️⃣ 에러 및 예외 처리
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 폼 검증 에러 | 필수값/시간 순서/겹침 오류 UI 검증 | ☐ |
+| 4-2 | API 오류 | HTTP 상태별 에러 메시지/재시도 UX 검증 | ☐ |
+| 4-3 | 접근성 실패 | 누락된 `id`/`aria-label` 검출 테스트 포함 | ☐ |
+
+---
+
+## 5️⃣ 테스트 기반성
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | 결정성 | 타이머/시간 고정(`vi.useFakeTimers` 등) | ☐ |
+| 5-2 | 독립성 | 케이스 간 상태 공유 금지, 파일 격리 | ☐ |
+| 5-3 | 성능 | 과도한 렌더/대기 회피, 최소한의 렌더로 검증 | ☐ |
+
+---
+
+## 6️⃣ 문서 품질
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 6-1 | 디렉토리 구조 | `src/__tests__/hooks|unit|integration` 위치 준수 | ☐ |
+| 6-2 | 파일명 규칙 | `*.spec.ts(x)` 네이밍, 대상과 1:1 매칭 | ☐ |
+| 6-3 | 주석 최소화 | 테스트 의도가 코드와 설명으로 충분히 드러남 | ☐ |
+
+---
+
+## 7️⃣ 프로젝트 맥락·영향
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 7-1 | 규칙 준수 | any 금지, 명시적 타입, 접근성, 성능 규칙 반영 | ☐ |
+| 7-2 | 회귀 테스트 | 영향 범위의 기존 테스트 보강 여부 | ☐ |
+
+---
+
+## 8️⃣ 프롬프트 품질
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 8-1 | 출력 형식 | 통과/실패 기준과 증빙(스크린샷/로그) 안내 | ☐ |
+| 8-2 | 금지사항 | 구현 세부 검사, 과도한 mock 금지 안내 | ☐ |
+
+---
+
+## 📎 산출물
+- 테스트 코드(PR 포함)
+- 커버리지 리포트
+- MSW 핸들러/목 데이터 업데이트 목록
+
+---
+
+## 📚 참고 문서
+- `.cursor/docs/tdd-document.md` — Red/Green/Refactor, Tidy First 커밋 분리
+- `.cursorrules` — 테스트 구조/한글 설명/AAA/UTC·ISO 규칙 준수
+
+## 🧾 제출 지침
+- 본 체크리스트로 1차 자체 점검 완료 후 결과 보고서를 다음 경로에 작성:
+ - `.cursor/outputs/test-code-review.md`
+- 보고서에는 다음을 포함:
+ - 시나리오 ↔ 테스트 케이스 매핑 표
+ - 커버리지 스냅샷 및 미달 항목과 사유
+ - 명령/로그: `pnpm test`, `pnpm test:coverage`
+
+
+---
+
+## 🚦 단계 게이트 (TDD & 승인)
+
+- [ ] Red: 실패 테스트 커밋 완료(필요 시 테스트 파일 상단 임시 ESLint 비활성화 주석 포함)
+ - 예: `/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */`
+- [ ] Green: 최소 구현으로 전체 테스트 통과, 임시 린트 예외 제거
+- [ ] Refactor: 테스트 코드 정리(중복 제거/명명 개선)
+- [ ] 자체 점검 체크리스트 전 항목 확인(✅)
+- [ ] CEO 승인 완료(`.cursor/outputs/ceo-approval.md` 반영)
+- [ ] 다음 단계로 전환: 코드작성 에이전트
+
+
diff --git a/.cursor/checklist/test-design-agent-checklist.md b/.cursor/checklist/test-design-agent-checklist.md
new file mode 100644
index 00000000..0b022409
--- /dev/null
+++ b/.cursor/checklist/test-design-agent-checklist.md
@@ -0,0 +1,126 @@
+# 🧪 테스트설계 에이전트 검증 체크리스트
+
+> Purpose: 기능설계 산출물과 프로젝트 규칙을 기반으로, 테스트 설계 산출물이 TDD·도메인·접근성·성능 요구사항을 충분히 반영하는지 사전 검증합니다. (1차 자체 점검 → CEO 승인)
+
+---
+
+## 1️⃣ 요구사항 명확성 (Requirement Clarity)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | 테스트 범위 정의 | 유닛(유틸), 훅, 컴포넌트, 통합 중 범위를 명확히 구분했는가 | ✅ |
+| 1-2 | 기능 시나리오 식별 | 사용자 플로우(생성/수정/삭제/검색/반복)별 Given-When-Then 정의 | ✅ |
+| 1-3 | 비기능 요구 반영 | 접근성, 성능(메모·렌더링), 에러/로딩 UX 요구 포함 | ✅ |
+| 1-4 | 승인 기준 명시 | 각 시나리오의 통과 조건과 실패 기준을 구체화 | ✅ |
+
+---
+
+## 2️⃣ 입력·출력 정의 (I/O Specification)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 입력 데이터와 타입 | 이벤트/반복/알림/검색 입력 구조와 타입, 필수값 명시 | ✅ |
+| 2-2 | 외부 경계/목 전략 | `fetchHolidays` 등 API 경계를 MSW/핸들러 전략과 함께 정의 | ✅ |
+| 2-3 | 출력 및 관찰 지표 | 화면/상태 변화, 접근성 속성, 알림 트리거 등 관찰 가능한 출력 | ✅ |
+| 2-4 | 시간·날짜 규칙 | UTC, ISO 날짜, 24시간, 윤년/월 경계/반복 종료일 포함 | ✅ |
+
+---
+
+## 3️⃣ 처리 로직 구체성 (Process Logic)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | 우선순위/커버리지 | 핵심 경로 우선, 엣지 케이스 포함, 커버리지 목표 제시 | ✅ |
+| 3-2 | 실패/예외 흐름 | 검증 실패, 겹침, API 오류, 네트워크 오류 시나리오 포함 | ✅ |
+| 3-3 | 의존성 분리 | 훅·유틸·컴포넌트 관심사 분리 가정으로 테스트 가능 상태 | ✅ |
+| 3-4 | 반복 일정 규칙 | 종료일 제한, 31일/2월29일 규칙, 단일/전체 수정/삭제 흐름 | ✅ |
+
+---
+
+## ♾️ 전역 엣지 케이스 커버리지 게이트 (Global Edge-Case Coverage Gate)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| EC-1 | 요구사항 엣지 케이스 추출 | `requirements`/`stories`에서 각 요구사항별 엣지 케이스 목록화 | ☐ |
+| EC-2 | 트레이스 매트릭스 | 요구사항 → 테스트 케이스 매핑 표 작성(누락 0개) | ☐ |
+| EC-3 | 경계값·형식·범위 | min/max, 포맷 오류, 빈 값, 월/윤년/주 경계 포함 | ☐ |
+| EC-4 | 오류/네트워크 | HTTP 오류, 타임아웃, 재시도/중단 기준 설계 | ☐ |
+| EC-5 | 접근성 엣지 | id/aria 누락, 라벨-컨트롤 연결 실패 시나리오 포함 | ☐ |
+| EC-6 | 성능 상한 | 데이터 최대 개수·렌더 비용 경계 및 UX 영향 정의 | ☐ |
+| EC-7 | 결정성 확보 | 시간/시드 고정, 비결정성 제거 전략 명시 | ☐ |
+| EC-8 | 승인 기준 | 각 엣지 케이스의 통과/실패 기준과 관찰 지표 명문화 | ☐ |
+
+---
+
+## 4️⃣ 에러 및 예외 처리 (Error Handling)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 오류 메시지 정책 | 코드/메시지 일관, 사용자 피드백(헬퍼텍스트/다이얼로그) 검증 | ✅ |
+| 4-2 | API 오류 매핑 | HTTP 상태별 처리, 재시도/중단 기준 정의 | ✅ |
+| 4-3 | 폼 검증 실패 | 필수값, 시간 순서, 겹침 검증 케이스 정의 | ✅ |
+
+---
+
+## 5️⃣ 테스트 기반성 (Testability)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | AAA 패턴 | 시나리오가 Arrange-Act-Assert로 변환 가능 | ✅ |
+| 5-2 | 결정적 테스트 | 타임존/시드 고정, 비결정성 제거 전략 명시 | ✅ |
+| 5-3 | 독립성 | 케이스 간 공유 상태/순서 의존 제거 | ✅ |
+| 5-4 | 접근성 검증 | id/aria-label/시맨틱 요소 관찰 포인트 포함 | ✅ |
+
+---
+
+## 6️⃣ 명세 문서 품질 (Documentation Quality)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 6-1 | 구조 일관성 | Feature/Input/Process/Output/Error 구조 유지 | ✅ |
+| 6-2 | 표 정합성 | 표 렌더링, 중복·모순 없음 | ✅ |
+| 6-3 | 용어 일관성 | 캘린더 도메인 용어 사용(이벤트, 반복, 알림 등) | ✅ |
+
+---
+
+## 7️⃣ 프로젝트 맥락 및 영향 분석 (Context & Impact)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 7-1 | 아키텍처 적합성 | 훅/유틸/컴포넌트 경계 준수 가정 | ✅ |
+| 7-2 | 회귀 범위 도출 | 영향 범위와 회귀 테스트 리스트 제시 | ✅ |
+| 7-3 | 성능 고려 | 메모·가상화·렌더링 최적화 검증 포인트 정의 | ✅ |
+
+---
+
+## 8️⃣ 에이전트 프롬프트 품질 (Prompt Quality)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 8-1 | 출력 형식 고정 | 표/체크박스 등 형식 요구 명확 | ✅ |
+| 8-2 | 금지사항 반영 | 구현 세부 테스트 금지, any 금지 등 규칙 반영 | ✅ |
+| 8-3 | 승인 게이트 | "자체 점검 ✅ 후 CEO 승인" 절차 포함 | ✅ |
+
+---
+
+## 📎 산출물 (Deliverables)
+- 테스트 시나리오 명세서(본 체크리스트와 함께 제출)
+- 엣지 케이스 목록(윤년, 월경계, 반복 규칙)
+- 목/핸들러 전략서(MSW)
+
+---
+
+## 📚 참고 문서
+- `.cursor/docs/tdd-document.md` — TDD 사이클 및 커밋 원칙 준수
+- `.cursorrules` — 타입/접근성/도메인/아키텍처 규칙 전면 준수
+
+## 🧾 제출 지침
+- 본 체크리스트로 1차 자체 점검 완료 후 결과 보고서를 다음 경로에 작성:
+ - `.cursor/outputs/test-design-review.md`
+- 보고서에는 다음을 포함:
+ - 체크 항목별 결과(✅/⚠️)와 근거(파일/라인/로그)
+ - 커버리지 목표/엣지 케이스 목록/모킹 전략 요약
+ - 실행 명령과 로그: `pnpm lint`, `pnpm test`, `pnpm build`
+
+---
+
+## 🚦 단계 게이트 (TDD & 승인)
+
+- [x] Red: 실패 테스트 설계 완료, 린트 예외 정책 문서화(테스트 파일 국한)
+- [x] Green: 구현 가이드(최소 구현 범위)와 통과 기준 명시
+- [x] 자체 점검 체크리스트 전 항목 확인(✅)
+- [x] CEO 승인 완료(`.cursor/outputs/ceo-approval.md` 반영)
+- [x] 다음 단계로 전환: 테스트코드 에이전트
+
+
diff --git a/.cursor/docs/architect-reference.md b/.cursor/docs/architect-reference.md
new file mode 100644
index 00000000..32537e3a
--- /dev/null
+++ b/.cursor/docs/architect-reference.md
@@ -0,0 +1,36 @@
+# Architect Reference Guide
+
+## 📌 목적
+본 문서는 기능 설계 담당 Architect 에이전트가 시스템 설계 시 따라야 할 원칙과 문서화 기준을 제공합니다.
+
+## 🧩 주요 설계 원칙
+- 시스템 구성 요소(Component)를 명확히 정의하고, 책임(Responsibility)을 분리한다.
+- 인터페이스(Interface)와 경계(Boundary)를 설계 초기부터 고려한다.
+- 비기능 요구사항(성능, 확장성, 보안 등)을 설계 문서에 반영한다.
+- 설계 결정(Architectural Decision)을 문서화하여 이유(Rationale)와 대안(Alternative)을 남긴다.
+- 설계 문서를 최신 상태로 유지하며 변경 이력을 기록한다.
+
+## 📁 문서화 구조 제안
+1. 개요 및 목표 (Overview)
+2. 시스템 컨텍스트 및 외부 연계 (Context)
+3. 구성요소(Container) 및 컴포넌트(Component) 구조
+4. 데이터 설계 (Data Model)
+5. 인터페이스 및 API 설계
+6. 주요 설계 결정 및 트레이드오프 (Architectural Decisions)
+7. 비기능 요구사항 및 품질 속성
+8. 운영·배포 아키텍처 및 인프라 (Deployment)
+9. 용어집 및 참고자료 (Glossary & References)
+
+## ✅ 설계 체크리스트
+- [ ] 기능 명세서의 요구사항을 설계에 반영했는가?
+- [ ] 각 구성요소의 책임이 명확히 정의되어 있는가?
+- [ ] 컴포넌트 간 의존성이 낮게 설계되었는가?
+- [ ] 인터페이스 명세가 명확하며 변경에 유연한가?
+- [ ] 성능/보안/확장성 등의 비기능 요구사항이 반영되었는가?
+- [ ] 설계 문서 내 설계 결정이 이유와 대안을 포함하고 있는가?
+- [ ] 문서가 최신 상태이며 버전/변경 이력이 남아 있는가?
+
+## 📎 참고 문서 및 자료
+- Software architecture documentation guide – Document360.
+- Best practices for software architecture design – Lucidchart.
+- Software architecture documentation: The ultimate guide – Working Software.
\ No newline at end of file
diff --git a/.cursor/docs/kent-beck-testing.md b/.cursor/docs/kent-beck-testing.md
new file mode 100644
index 00000000..04d38efa
--- /dev/null
+++ b/.cursor/docs/kent-beck-testing.md
@@ -0,0 +1,89 @@
+# 🧠 Kent Beck's Test Design Philosophy (TDD Core Guide)
+
+> "Tests are a **design tool for software**, the **specification of behavior**, and a **bulwark against regression**." — Kent Beck
+
+---
+
+## 1️⃣ Purpose of Testing
+
+In TDD, testing is **not a means to find bugs, but a compass to guide correct design**.
+
+| Category | Description |
+|------|------|
+| 🎯 **Specification Test** | Tests that define "what the function should do" |
+| 🔍 **Regression Test** | Tests that ensure already working functionality is not broken |
+| 🧩 **Design Driver Test** | Tests that drive the implementation toward a simpler, more modular direction |
+
+---
+
+## 2️⃣ 3 Stages of Test Design (Red → Green → Refactor)
+
+| Stage | Description | Action Example |
+|------|------|-----------|
+| 🔴 **Red** | Write a failing test first. | Declare, "This feature must work," first. |
+| 🟢 **Green** | Write the minimum code necessary to pass the test. | Defer complex logic; focus on 'pass' for now. |
+| 🟣 **Refactor** | Remove duplication, improve clarity. | Refactor the code to make the test maintainable. |
+
+> 💬 "Tests make you design the code, and Refactoring makes you clean up the design."
+
+---
+
+## 3️⃣ 5 Principles of Good Tests (Kent Beck + xUnit Patterns)
+
+| Principle | Description |
+|------|------|
+| **Fast** | Tests should run quickly. (Provide immediate feedback) |
+| **Independent** | Tests should not affect each other. |
+| **Repeatable** | Running the test anytime, anywhere must yield the same result. |
+| **Self-validating** | The result must be clearly defined as pass/fail. |
+| **Timely** | Tests should be written *just before* writing the code. |
+
+> ✅ Mnemonic: **F.I.R.S.T. Principles**
+
+---
+
+## 4️⃣ F.I.R.S.T. Principles and Code Examples
+
+| Principle | Description (Emphasis) | Bad Test ❌ (Anti-Pattern) | Good Test ✅ (Best Practice) |
+|------|------|------|------|
+| **Fast** | Tests must execute quickly. | Tests that connect to the **network/database** every time. (Takes seconds) | Tests using **Mocking** or an **In-memory DB**. (Takes milliseconds) |
+| **Independent** | Tests should not depend on the order or state of others. | A test where `testDeleteUser()` only succeeds if `testCreateUser()` runs first. | Ensures an **isolated environment** by performing independent data setup (Setup/Teardown) for each test. |
+| **Repeatable** | Must guarantee the same result regardless of when run. | Tests that rely on the current **time** or **random numbers**, changing results upon execution. | Mocks the `Time Provider` or uses a fixed Seed to ensure only **predictable values** are used. |
+| **Self-validating** | The result must clearly be `PASS` or `FAIL`. | A test that requires **manual checking** of logs or files after execution. | Returns an **explicit boolean result** like `Assert.Equals(expected, actual)`. |
+| **Timely** | Tests must be written *just before* coding. | Tests written to **meet coverage goals** after functionality is complete or just before release. | Starts in a **Red (failing) state**, acting as the **functional specification** before design is finished. |
+
+---
+
+## 5️⃣ Test Design Checklist
+
+| Category | Key Question | Example |
+|------|------------|------|
+| **Functional Scope** | Are all functional requirements expressed as tests? | Includes the case of "handling the 31st when selecting a recurring schedule" |
+| **Input/Output Definition** | Are the input values and expected results clear? | Specify date, recurrence type, end date, etc. |
+| **Edge Cases** | Inclusion of boundary values / exceptional cases | February 29th on a leap year, invalid end date, etc. |
+| **Independence** | Is there no dependency between tests? | DB initialization or using Mocks |
+| **Clarity** | Does the test name read like a specification? | `it('31st is only created in months that have 31 days')` |
+
+---
+
+## 6️⃣ Test Naming Convention (Behavior Driven)
+
+> Test names are written from the "**user's perspective**."
+
+| Example | Bad Example ❌ | Good Example ✅ |
+|------|-------------|------------|
+| Recurrence End Validation | `testEndDateLogic()` | `it('should return an error if the end date is before the start date')` |
+| Recurrence Interval Validation | `testInterval()` | `it('should fail validation if the recurrence interval is less than 1')` |
+
+> 💬 "One should be able to understand the functionality just by reading the test names."
+
+---
+
+## 7️⃣ Test Design Approaches
+
+| Approach | Description | Example |
+|------|------|------|
+| **Example-Driven Design** | Define tests based on concrete examples | 31st $\rightarrow$ created only in specific months |
+| **Boundary Testing** | Test minimum, maximum, and boundary values | `2025-12-31`, `2024-02-29` |
+| **Error-Driven Design** | Write failure cases first | Invalid date $\rightarrow$ error occurs |
+| **Goal-Oriented Design** | Group tests by requirement units | Group cases by recurrence type
\ No newline at end of file
diff --git a/.cursor/docs/tdd-document.md b/.cursor/docs/tdd-document.md
new file mode 100644
index 00000000..ab191ba4
--- /dev/null
+++ b/.cursor/docs/tdd-document.md
@@ -0,0 +1,77 @@
+Always follow the instructions in plan.md. When I say "go", find the next unmarked test in plan.md, implement the test, then implement only enough code to make that test pass.
+
+# ROLE AND EXPERTISE
+
+You are a senior software engineer who follows Kent Beck's Test-Driven Development (TDD) and Tidy First principles. Your purpose is to guide development following these methodologies precisely.
+
+# CORE DEVELOPMENT PRINCIPLES
+
+- Always follow the TDD cycle: Red → Green → Refactor
+- Write the simplest failing test first
+- Implement the minimum code needed to make tests pass
+- Refactor only after tests are passing
+- Follow Beck's "Tidy First" approach by separating structural changes from behavioral changes
+- Maintain high code quality throughout development
+
+# TDD METHODOLOGY GUIDANCE
+
+- Start by writing a failing test that defines a small increment of functionality
+- Use meaningful test names that describe behavior (e.g., "shouldSumTwoPositiveNumbers")
+- Make test failures clear and informative
+- Write just enough code to make the test pass - no more
+- Once tests pass, consider if refactoring is needed
+- Repeat the cycle for new functionality
+
+# TIDY FIRST APPROACH
+
+- Separate all changes into two distinct types:
+ 1. STRUCTURAL CHANGES: Rearranging code without changing behavior (renaming, extracting methods, moving code)
+ 2. BEHAVIORAL CHANGES: Adding or modifying actual functionality
+- Never mix structural and behavioral changes in the same commit
+- Always make structural changes first when both are needed
+- Validate structural changes do not alter behavior by running tests before and after
+
+# COMMIT DISCIPLINE
+
+- Only commit when:
+ 1. ALL tests are passing
+ 2. ALL compiler/linter warnings have been resolved
+ 3. The change represents a single logical unit of work
+ 4. Commit messages clearly state whether the commit contains structural or behavioral changes
+- Use small, frequent commits rather than large, infrequent ones
+
+# CODE QUALITY STANDARDS
+
+- Eliminate duplication ruthlessly
+- Express intent clearly through naming and structure
+- Make dependencies explicit
+- Keep methods small and focused on a single responsibility
+- Minimize state and side effects
+- Use the simplest solution that could possibly work
+
+# REFACTORING GUIDELINES
+
+- Refactor only when tests are passing (in the "Green" phase)
+- Use established refactoring patterns with their proper names
+- Make one refactoring change at a time
+- Run tests after each refactoring step
+- Prioritize refactorings that remove duplication or improve clarity
+
+# EXAMPLE WORKFLOW
+
+When approaching a new feature:
+1. Write a simple failing test for a small part of the feature
+2. Implement the bare minimum to make it pass
+3. Run tests to confirm they pass (Green)
+4. Make any necessary structural changes (Tidy First), running tests after each change
+5. Commit structural changes separately
+6. Add another test for the next small increment of functionality
+7. Repeat until the feature is complete, committing behavioral changes separately from structural ones
+
+Follow this process precisely, always prioritizing clean, well-tested code over quick implementation.
+
+Always write one test at a time, make it run, then improve structure. Always run all the tests (except long-running tests) each time.
+
+# Rust-specific
+
+Prefer functional programming style over imperative style in Rust. Use Option and Result combinators (map, and_then, unwrap_or, etc.) instead of pattern matching with if let or match when possible.
diff --git a/.cursor/docs/testing-library-queries-priority.md b/.cursor/docs/testing-library-queries-priority.md
new file mode 100644
index 00000000..dd07457b
--- /dev/null
+++ b/.cursor/docs/testing-library-queries-priority.md
@@ -0,0 +1,50 @@
+# Testing Library Query Priority Guide
+
+## 📚 Overview
+
+Testing Library provides several query methods for selecting DOM elements. This document explains **which query should be used first (priority)** and the **characteristics of each query**.
+
+---
+
+## 🔍 Query Types and Characteristics
+
+### ✅ Basic Query Types
+
+* `getBy...`: Must find exactly one matching element; throws an error if none or multiple are found.
+* `queryBy...`: Returns `null` if no element is found; throws an error if multiple are found.
+* `findBy...`: Finds elements asynchronously and returns a Promise.
+* `getAllBy...`, `queryAllBy...`, `findAllBy...`: Versions for finding multiple elements.
+
+### 🎯 Priority (based on query predicate)
+
+Testing Library recommends query methods that are as **semantic and accessibility-friendly** as possible.
+
+The general priority is as follows:
+
+1. `getByRole(…, { name: … })`
+2. `getByLabelText(…)`
+3. `getByPlaceholderText(…)`
+4. `getByText(…)`
+5. `getByDisplayValue(…)`
+6. `getByAltText(…)`, `getByTitle(…)`
+7. `getByTestId(…)` — Use as a last resort whenever possible
+
+> Note: Refer to the “Which query should I use?” section for more details.
+
+---
+
+## 🧠 Why is Role the Priority?
+
+* From an **Accessibility** perspective, elements with a designated role are linked with screen readers and other assistive technologies.
+* The combination of `getByRole` + `name` option can appropriately select a wide range of elements.
+* Queries that rely on `testId`, class names, or IDs are easily broken by implementation changes and are therefore considered the last option.
+
+---
+
+## ⚙️ Practical Guidelines
+
+* Prioritize **Role**-based queries whenever possible.
+
+```ts
+// Recommended
+screen.getByRole('button', { name: /submit/i });
\ No newline at end of file
diff --git a/.cursor/outputs/ceo-approval.md b/.cursor/outputs/ceo-approval.md
new file mode 100644
index 00000000..7e3640b2
--- /dev/null
+++ b/.cursor/outputs/ceo-approval.md
@@ -0,0 +1,42 @@
+# 🧾 CEO 최종 승인 기록
+
+## 메타데이터
+- 승인자: CEO
+- 날짜: 2025-10-28
+- 관련 커밋/PR:
+- 참고 문서: `.cursor/docs/tdd-document.md`, `.cursorrules`
+
+---
+
+## 단계별 결과 수집
+| 단계 | 보고서 경로 | 자체 점검 | CEO 판단 | 코멘트 |
+|------|-------------|-----------|----------|--------|
+| 기능설계 검증 | `.cursor/outputs/feature-verification-review.md` | ✅ | 승인 | 요구사항/입출력/로직/예외/테스트/맥락 모두 적합 |
+| 테스트설계 | `.cursor/outputs/test-design-review.md` | ✅ | 승인 | 설계 문서 기준 11개 케이스 커버, 엣지/에러/게이트 명확 |
+| 테스트코드 | `.cursor/outputs/test-code-review.md` | ✅/⚠️ | 승인/보류/반려 | |
+| 코드작성 | `.cursor/outputs/code-implementation-review.md` | ✅/⚠️ | 승인/보류/반려 | |
+| 리팩토링 | `.cursor/outputs/refactoring-review.md` | ✅/⚠️ | 승인/보류/반려 | |
+| 오케스트레이션 | `.cursor/outputs/orchestration-review.md` | ✅/⚠️ | 승인/보류/반려 | |
+
+---
+
+## 품질 게이트 확인
+- `pnpm lint`: 통과/실패 (로그 링크/요약)
+- `pnpm test`: 통과/실패, 커버리지 (요약)
+- `pnpm build`: 통과/실패 (로그)
+
+---
+
+## 도메인·규칙 준수 판단
+- 날짜/시간: ISO(YYYY-MM-DD), 24h, UTC 검증 기준 반영
+- 반복 일정: 종료일 제한, 31일/윤년, 그룹 관리 규칙 준수
+- 접근성: ARIA 라벨·시맨틱 HTML·MUI 사용 원칙 반영
+- 타입/아키텍처: 엄격 TS, 관심사 분리, 하위호환 타입 보강 확인
+
+---
+
+## 최종 결정
+- 전체 상태: 승인(기능설계, 테스트설계 단계)
+- 조건/비고: 테스트코드/코드작성/리팩토링/오케스트레이션은 별도 승인.
+
+
diff --git a/.cursor/outputs/code-implementation-review.md b/.cursor/outputs/code-implementation-review.md
new file mode 100644
index 00000000..06c0b61a
--- /dev/null
+++ b/.cursor/outputs/code-implementation-review.md
@@ -0,0 +1,51 @@
+# 🧩 코드작성 결과 보고서 (스토리 02/03/04/05 진행)
+
+## 메타데이터
+- 에이전트: 코드작성
+- 날짜: 2025-10-30
+- 참고 문서: `.cursor/checklist/code-implementation-agent-checklist.md`
+
+---
+
+## 요약
+- 주요 변경 파일:
+ - `src/App.tsx` (반복 아이콘 표시, 반복 수정/삭제 다이얼로그)
+ - `src/hooks/useEventOperations.ts` (저장/삭제 분기 옵션 추가)
+ - `src/utils/repeatGeneration.ts` (종료일 상수/유틸 도입)
+ - 테스트: UI/훅/유닛 추가·보완
+- 테스트 통과 여부: 타깃 테스트 Green(반복 UI·수정·삭제, 종료일 유틸)
+- 빌드: 생략(요청에 따라 린트/빌드 스킵)
+
+---
+
+## 규칙 준수 근거
+| 영역 | 규칙 | 근거 | 코멘트 |
+|------|------|------|--------|
+| 타입 | any 금지/명시적 타입 | `types.ts` 유니온/인터페이스 | any 미사용 |
+| 구조 | 훅/유틸/컴포넌트 경계 | 폴더 구조 준수 | 관심사 분리 유지 |
+| 접근성 | id/aria/시맨틱 | `App.tsx` 아이콘/버튼 라벨 | `aria-label` 적용 |
+| 도메인 | ISO/UTC/24h/윤년/월경계 | `repeatGeneration.ts` | 상수화/클램프 |
+| 반복 | 수정/삭제 분기 | 훅/통합 테스트 | 단일/전체 분기 구현 |
+
+---
+
+## 실행 로그(요약)
+```bash
+# 스토리 02/03
+pnpm test -t "반복 아이콘 표시"
+pnpm test -t "clampToSystemMaxEndDate|getEffectiveEndDate"
+
+# 스토리 04
+pnpm test -t "반복 일정 수정 플로우|반복 일정 수정 분기"
+
+# 스토리 05
+pnpm test -t "삭제 분기"
+```
+
+---
+
+## 결론
+- 상태: 통과(타깃 테스트)
+- 후속 조치: 삭제 UI 다이얼로그 테스트 추가 및 리팩터 기회 재평가
+
+
diff --git a/.cursor/outputs/feature-verification-review.md b/.cursor/outputs/feature-verification-review.md
new file mode 100644
index 00000000..4388366f
--- /dev/null
+++ b/.cursor/outputs/feature-verification-review.md
@@ -0,0 +1,93 @@
+# 기능설계 검증 보고서 - 반복 일정 표시 (스토리 02)
+
+- 문서 범위: 스토리 02(반복 일정 표시)만 해당
+- 기준 문서: `.cursor/checklist/feature-verification.md`
+- 참조 스토리: `.cursor/spec/stories/02-repeat-event-display.md`
+- 대상 브랜치: main
+- 작성일: 2025-10-30
+- 작성자: Sion(architect)
+
+## 1️⃣ 요구사항 명확성 (Requirement Clarity)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 1-1 | 요구사항이 명시적으로 표현되어 있는가 | 반복 일정은 주/월 뷰와 우측 리스트에서 아이콘으로 구분 | ✅ |
+| 1-2 | 비기능 요구사항이 포함되어 있는가 | 접근성 `aria-label="반복 일정"`, 테마 색상 사용 명시 | ✅ |
+| 1-3 | 테스트 가능한 형태로 변환 가능한가 | 주/월/리스트에서 아이콘 노출 여부를 질의로 검증 가능 | ✅ |
+
+- 비고: 아이콘 우선순위(알림 > 반복)도 구체적으로 명시됨.
+
+## 2️⃣ 입력·출력 정의 (I/O Specification)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 2-1 | 입력값이 명시되어 있는가 | `event.repeat.type !== 'none'` 조건을 입력으로 간주 | ✅ |
+| 2-2 | 출력값이 명시되어 있는가 | UI에 Repeat 아이콘 렌더(주/월/리스트) | ✅ |
+| 2-3 | 입력과 출력이 논리적으로 일관적인가 | 반복이면 아이콘, 아니면 미노출 | ✅ |
+| 2-4 | 데이터 타입 정의가 명확한가 | 기존 `Event.repeat.type` 유니온 타입 사용 | ✅ |
+
+- 비고: 서버 스키마 변경 없음. 표시 로직만 해당.
+
+## 3️⃣ 처리 로직 구체성 (Process Logic)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 3-1 | 처리 단계가 순서대로 정의되어 있는가 | 이벤트 필터링 → 컴포넌트 배지 렌더링 → 아이콘 표기 | ✅ |
+| 3-2 | 단계가 테스트 가능하게 서술되어 있는가 | 각 뷰에서 쿼리(`getByLabelText`)로 검증 가능 | ✅ |
+| 3-3 | 조건 분기(예외 흐름) 포함 | 알림 아이콘 병행 시 우선순위/순서 규칙 포함 | ✅ |
+
+- 비고: 생성·검증 로직은 범위 밖임을 명시하여 경계가 명확함.
+
+## 4️⃣ 에러 및 예외 처리 (Error Handling)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 4-1 | 주요 실패 케이스 정의 | 반복인데 아이콘 미노출, aria-label 누락 | ✅ |
+| 4-2 | 에러 응답 형식 일관 | UI 레벨이므로 스낵바·네트워크 비해당 | ✅ |
+| 4-3 | 성공/실패 응답 구분 | 테스트 통과/실패로 판단 가능 | ✅ |
+
+- 비고: 네트워크 의존성 없음(표시 전용).
+
+## 5️⃣ 테스트 기반성 (Testability)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 5-1 | 기능 단위가 작고 독립 | 표시 로직만 커버, 다른 기능과 결합 낮음 | ✅ |
+| 5-2 | 기대 결과 명확 | 아이콘 존재/부재, DOM 순서 검증 | ✅ |
+| 5-3 | 시나리오 도출 가능 | 주/월/리스트 각각 Given-When-Then 전개 | ✅ |
+
+- 비고: 접근성 쿼리 우선순위 준수 가능.
+
+## 6️⃣ 명세 문서 품질 (Documentation Quality)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 6-1 | 섹션 구조 일관 | 목적/시나리오/표시 규칙/접근성/테스트 케이스 | ✅ |
+| 6-2 | 표 형식 정상 | 본 문서 및 스토리 문서 표기 정상 | ✅ |
+| 6-3 | 중복·모순 없음 | 다른 스토리와 역할 경계 명확 | ✅ |
+| 6-4 | 요약 명확 | 헤더와 목적에 기능 취지 명확 | ✅ |
+
+## 7️⃣ 프로젝트 맥락 및 영향 분석 (Context & Impact)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 7-1 | 도메인 용어 일관 | repeat, 알림, 주/월 뷰 용어 일치 | ✅ |
+| 7-2 | 기존 기능과 충돌 없음 | 렌더 레이어만 변경, 저장/검증 무영향 | ✅ |
+| 7-3 | 기능 경계 명확 | 표시 로직 vs 생성/수정/삭제 분리 | ✅ |
+| 7-4 | 기존 동작 영향 없음 | 이벤트 목록/필터 흐름 유지 | ✅ |
+| 7-5 | 영향 범위 명시 | `App.tsx` 렌더 섹션(주/월/리스트) | ✅ |
+| 7-6 | 회귀 테스트 필요 식별 | 알림 아이콘 우선순위와의 조합 | ✅ |
+
+## 8️⃣ 에이전트 품질 점검 (Prompt Quality)
+| 번호 | 항목 | 설명 | 점검 |
+|------|------|------|------|
+| 8-1 | 형식 요구 일관 | 체크리스트 기반 보고 구조 유지 | ✅ |
+| 8-2 | 언어·포맷 지시 명확 | 한국어, 테이블/코드블록 규칙 준수 | ✅ |
+| 8-3 | 일반 서술 제한 | 구체 규칙·검증 대상 위주 기술 | ✅ |
+| 8-4 | 프로젝트 규칙 일치 | 접근성·테마·관심사분리 원칙 부합 | ✅ |
+
+---
+
+## 최종 검증 결과 요약
+- 요구사항 적합: 적합(✅)
+- 시스템 정합성: UI 레이어 한정, 기존 흐름과 정합(✅)
+- 테스트 준비도: 주/월/리스트 단위·통합 테스트 도출 완료(✅)
+- 영향도: 낮음(렌더 로직 추가)
+
+## 보완/메모
+- 실제 구현 시 `@mui/icons-material/Repeat` 도입 및 `aria-label` 부여 필수.
+- DOM 순서 검증(알림 > 반복)을 테스트에 포함.
+
diff --git a/.cursor/outputs/orchestration-review.md b/.cursor/outputs/orchestration-review.md
new file mode 100644
index 00000000..6632cabf
--- /dev/null
+++ b/.cursor/outputs/orchestration-review.md
@@ -0,0 +1,48 @@
+# 🧵 오케스트레이션 결과 보고서
+
+## 메타데이터
+- 에이전트: 오케스트레이션
+- 날짜:
+- 관련 커밋/PR:
+- 참고 문서: `.cursor/docs/tdd-document.md`, `.cursorrules`
+- 사용 체크리스트: `.cursor/checklist/orchestration-agent-checklist.md`
+
+---
+
+## 단계별 산출물 확인
+| 단계 | 산출물 | 경로 | 자체 점검 결과 | 확인 |
+|------|--------|------|----------------|------|
+| 테스트설계 | 결과 보고서 | `.cursor/outputs/test-design-review.md` | ✅/⚠️ | ☐ |
+| 테스트코드 | 결과 보고서 | `.cursor/outputs/test-code-review.md` | ✅/⚠️ | ☐ |
+| 코드작성 | 결과 보고서 | `.cursor/outputs/code-implementation-review.md` | ✅/⚠️ | ☐ |
+| 리팩토링 | 결과 보고서 | `.cursor/outputs/refactoring-review.md` | ✅/⚠️ | ☐ |
+
+---
+
+## 품질 게이트 실행 로그
+```bash
+pnpm lint
+```
+
+```bash
+pnpm test
+```
+
+```bash
+pnpm build
+```
+
+---
+
+## 리스크/의사결정
+| 리스크 | 영향 | 대응 | 상태 |
+|--------|------|------|------|
+| | | | |
+
+---
+
+## 결론
+- 상태: 통과 / 보완 필요
+- 후속 조치:
+
+
diff --git a/.cursor/outputs/refactoring-review.md b/.cursor/outputs/refactoring-review.md
new file mode 100644
index 00000000..f14ee28f
--- /dev/null
+++ b/.cursor/outputs/refactoring-review.md
@@ -0,0 +1,47 @@
+# 🔧 리팩토링 결과 보고서 (스토리 02/03/04/05 정리)
+
+## 메타데이터
+- 에이전트: 리팩토링
+- 날짜: 2025-10-30
+- 사용 체크리스트: `.cursor/checklist/refactoring-agent-checklist.md`
+
+---
+
+## 요약
+- 대상 범위: 반복 아이콘(UI), 반복 수정/삭제 다이얼로그(UI), 저장/삭제 훅 분기(로직)
+- 목적/효과: 중복 제거, inline 핸들러 분리, 매직 문자열 제거, 가독성/유지보수성 향상
+- 테스트 결과: 타깃 테스트 Green 유지
+
+---
+
+## 전/후 비교
+| 항목 | 변경 전 | 변경 후 | 기대 효과 | 근거 |
+|------|--------|--------|----------|------|
+| UI 중복 | 반복 아이콘/다이얼로그 inline 로직 | `RepeatIconIfNeeded`, 확인/삭제 핸들러 분리 | 중복 제거, 접근성 라벨 일관 | `src/App.tsx` |
+| 매직 문자열 | 종료일 '2025-12-31' 하드코딩 | `MAX_REPEAT_END_DATE` 상수 | 변경 용이/오류 예방 | `repeatGeneration.ts` |
+| 핸들러 구조 | inline onClick 다수 | `useCallback` 핸들러(`handleConfirm*`, `requestDelete`) | 재사용/성능/가독성 향상 | `src/App.tsx` |
+| 훅 옵션 | 분기 인자 없음 | `saveEvent(event, { scope })`, `deleteEvent(id, { scope, repeatId })` | 단일/전체 분기 명확 | `useEventOperations.ts` |
+
+---
+
+## 퍼블릭 API 안정성
+- 시그니처 변경 여부: 내부 훅 API에 선택적 옵션 추가(하위호환 유지)
+- 대응 조치: 기존 호출 경로는 변경 없이 동작, 신규 분기를 테스트로 보장
+
+---
+
+## 실행 로그(요약)
+```bash
+pnpm test -t "반복 아이콘 표시"
+pnpm test -t "clampToSystemMaxEndDate|getEffectiveEndDate"
+pnpm test -t "반복 일정 수정 플로우|반복 일정 수정 분기"
+pnpm test -t "삭제 분기|반복 일정 삭제 플로우"
+```
+
+---
+
+## 결론
+- 상태: 통과(Green 유지)
+- 후속 조치: 삭제 UI 추가 케이스(단일/전체 후 리스트 반영) 보강 검토
+
+
diff --git a/.cursor/outputs/test-code-review.md b/.cursor/outputs/test-code-review.md
new file mode 100644
index 00000000..573c1f49
--- /dev/null
+++ b/.cursor/outputs/test-code-review.md
@@ -0,0 +1,56 @@
+# 🧪 테스트코드 결과 보고서 (스토리 02/03 완료, 스토리 04 진행 중)
+
+## 메타데이터
+- 에이전트: 테스트코드
+- 날짜: 2025-10-30
+- 관련 커밋/PR: 반복 아이콘 표시/종료일 기본·클램프/반복 수정 다이얼로그 노출(UI)
+- 참고 문서: `.cursor/spec/stories/02-repeat-event-display.md`, `.cursor/spec/stories/03-repeat-end-condition.md`
+- 사용 체크리스트: `.cursor/checklist/test-code-agent-checklist.md`
+
+---
+
+## 요약
+- 통과(✅): 반복 아이콘 표시 2건, 종료일 기본/클램프 5건, 반복 수정 다이얼로그 노출 1건 (타깃 실행 기준)
+- 실패(❌): 없음(스토리 04 분기/호출 케이스는 다음 단계에서 Red 예정)
+- 커버리지: 단위 타깃 기준 적정, 전체 커버리지는 별도 실행 시 산출
+
+---
+
+## 시나리오 ↔ 테스트 매핑
+| 시나리오 | 테스트 파일 | 케이스명 | 상태 | 근거 |
+|----------|-------------|---------|------|------|
+| 반복 아이콘 표시 | `src/__tests__/medium.repeat-ui.spec.tsx` | 월/리스트에 반복 아이콘 노출 | ✅ | vitest -t "반복 아이콘 표시" |
+| 종료일 기본값 | `src/__tests__/unit/easy.repeatEndDefault.spec.ts` | 미입력 → 2025-12-31 | ✅ | vitest -t getEffectiveEndDate |
+| 종료일 클램프 | `src/__tests__/unit/easy.repeatEndClamp.spec.ts` | 2026-01-01 → 2025-12-31 | ✅ | vitest -t clampToSystemMaxEndDate |
+| 반복 수정(UI) | `src/__tests__/medium.repeat-update-ui.spec.tsx` | 저장 시 단일/전체 다이얼로그 노출 | ✅ | vitest -t "반복 일정 수정 플로우" |
+
+---
+
+## 커버리지 스냅샷
+```text
+타깃 실행(부분)으로 커버리지 수집 생략. 전체 사이클 종료 후 수집 예정.
+```
+
+---
+
+## 접근성/비기능 검증
+- 접근성 속성(id/aria/시맨틱) 검증 케이스: 반복 아이콘 `aria-label="반복 일정"` 확인
+- 결정성 확보(타임존/타이머/시드): `setupTests.ts`에서 TZ=UTC, 고정 타이머 적용
+
+---
+
+## 실행 로그(요약)
+```bash
+pnpm test -t "반복 아이콘 표시"
+pnpm test -t "getEffectiveEndDate"
+pnpm test -t "clampToSystemMaxEndDate"
+pnpm test -t "반복 일정 수정 플로우"
+```
+
+---
+
+## 결론
+- 상태: 통과(스토리 02/03), 스토리 04 일부 통과(UI 다이얼로그 노출)
+- 후속 조치: 스토리 04 훅/분기 실패 테스트(단일/전체 호출 분기) 추가 후 구현
+
+
diff --git a/.cursor/outputs/test-design-review.md b/.cursor/outputs/test-design-review.md
new file mode 100644
index 00000000..1e9c8291
--- /dev/null
+++ b/.cursor/outputs/test-design-review.md
@@ -0,0 +1,81 @@
+# 🧪 테스트설계 결과 보고서 (스토리 04 - 반복 일정 수정)
+
+## 메타데이터
+- 에이전트: 테스트설계
+- 날짜: 2025-10-30
+- 승인자: CEO(Riku)
+- 참고 문서: `.cursor/spec/stories/04-repeat-event-update.md`, `.cursorrules`
+- 사용 체크리스트: `.cursor/checklist/test-design-agent-checklist.md`
+
+---
+
+## 요약
+- 목표: 단일/전체 수정 분기에 대한 훅/통합 테스트 설계 확정
+- 상태: 보완 필요(테스트 케이스 정의 완료, 아직 Red 단계 미작성)
+
+---
+
+## 상세 체크 결과 (수신: Riku)
+| 섹션 | 번호 | 항목 | 결과(✅/⚠️) | 근거 | 코멘트 |
+|------|------|------|------------|------|--------|
+| 요구사항 | 1-1 | 테스트 범위 정의 | ✅ | 스토리 04 문서 | 훅/UI 분리 설계 |
+| I/O | 2-1 | 입력/타입 명확성 | ✅ | `src/types.ts` | `repeat.id` 포함 |
+| 프로세스 | 3-1 | 분기 정의 | ✅ | 스토리 04 문서 | 단일 vs 전체 |
+| 에러 | 4-1 | 실패 경로 | ⚠️ | (추가 예정) | 네트워크 실패 주입 |
+| 접근성 | 5-1 | 다이얼로그/버튼 라벨 | ✅ | 스토리 04 문서 | `aria-label` 명시 |
+
+---
+
+## 테스트 케이스(계획)
+- 훅: 편집 모드에서
+ - 단일 선택 → `PUT /api/events/:id`, 본문 `repeat.type='none'`
+ - 전체 선택 → `PUT /api/recurring-events/:repeatId`
+- UI: 저장 시 다이얼로그 노출, 버튼 라벨, 아이콘 유지/제거 검증
+
+---
+
+## 결론
+- 상태: 보완 필요(다음 단계에서 Red 작성)
+- 후속 조치: 훅/통합 실패 테스트 추가 후 보고
+
+---
+
+# 🧪 테스트설계 결과 보고서 (스토리 05 - 반복 일정 삭제)
+
+## 메타데이터
+- 에이전트: 테스트설계
+- 날짜: 2025-10-30
+- 승인자: CEO(Riku)
+- 참고 문서: `.cursor/spec/stories/05-repeat-event-delete.md`, `.cursorrules`
+- 사용 체크리스트: `.cursor/checklist/test-design-agent-checklist.md`
+
+---
+
+## 요약
+- 목표: 단일/전체 삭제 분기에 대한 훅/통합 테스트 설계 확정
+- 상태: 보완 필요(테스트 케이스 정의 완료, Red 단계 미작성)
+
+---
+
+## 상세 체크 결과 (수신: Riku)
+| 섹션 | 번호 | 항목 | 결과(✅/⚠️) | 근거 | 코멘트 |
+|------|------|------|------------|------|--------|
+| 요구사항 | 1-1 | 테스트 범위 정의 | ✅ | 스토리 05 문서 | 훅/UI 분리 설계 |
+| I/O | 2-1 | 입력/타입 명확성 | ✅ | `src/types.ts` | `repeat.id` 포함 |
+| 프로세스 | 3-1 | 분기 정의 | ✅ | 스토리 05 문서 | 단일 vs 전체 |
+| 에러 | 4-1 | 실패 경로 | ⚠️ | (추가 예정) | 네트워크 실패 주입 |
+| 접근성 | 5-1 | 다이얼로그/버튼 라벨 | ✅ | 스토리 05 문서 | `aria-label` 명시 |
+
+---
+
+## 테스트 케이스(계획)
+- 훅: 삭제 분기
+ - 단일 삭제 → `DELETE /api/events/:id`
+ - 전체 삭제 → `DELETE /api/recurring-events/:repeatId`
+- UI: 삭제 버튼 클릭 시 다이얼로그 노출, `단일 삭제`/`전체 삭제` 선택에 따른 리스트 변화/호출 검증
+
+---
+
+## 결론
+- 상태: 보완 필요(다음 단계에서 Red 작성)
+- 후속 조치: 훅/통합 실패 테스트 추가 후 보고
\ No newline at end of file
diff --git a/.cursor/spec/requirements/repeat-event-feature.md b/.cursor/spec/requirements/repeat-event-feature.md
new file mode 100644
index 00000000..33f9ead4
--- /dev/null
+++ b/.cursor/spec/requirements/repeat-event-feature.md
@@ -0,0 +1,78 @@
+---
+title: 반복 일정 기능 요구사항
+category: calendar
+type: requirement
+updated: 2025-10-26
+---
+
+# 📅 반복 일정 기능 요구사항
+
+## 🎯 목표
+기존 캘린더 시스템에 **반복 일정 기능**을 추가한다.
+사용자는 일정 생성 또는 수정 시 반복 유형을 설정하고, 반복 종료일 및 수정·삭제 방식을 제어할 수 있어야 한다.
+
+---
+
+## 🧩 주요 기능
+
+### 1️⃣ 반복 유형 선택
+- 사용자는 일정 생성 또는 수정 시 **반복 유형**을 선택할 수 있다.
+- 반복 유형 옵션:
+ - **매일 (daily)**
+ - **매주 (weekly)**
+ - **매월 (monthly)**
+ - **매년 (yearly)**
+- **특수 조건**
+ - “매월 D일” 선택 시 → 해당 달에 D일이 존재할 때만 생성(보정 생성 금지)
+ - 예: 31일 → 31일이 있는 달에만 생성, 30일 → 2월은 제외, 29일 → 평년 2월은 제외
+ - “윤년 2월 29일”에 **매년** 선택 시 → 윤년일 경우에만 생성
+ - “평년 2월 28일”에 **매년** 선택 시 → 평년일 경우에만 생성(윤년은 제외)
+ - 반복 일정은 **일정 겹침(overlap)** 을 고려하지 않는다.
+
+---
+
+### 2️⃣ 반복 일정 표시
+- 캘린더 뷰에서 반복 일정은 **반복 아이콘**으로 표시된다.
+- 일반 일정과 구분되도록 UI 상에서 시각적 차별화 필요.
+ - 예: 🔁, ↻, 또는 별도 컬러 표시
+
+---
+
+### 3️⃣ 반복 종료 조건
+- 사용자는 **반복 종료 조건**을 설정할 수 있다.
+- 종료 조건 옵션:
+ - 특정 날짜까지
+- 시스템 제약:
+ - 최대 반복 종료일은 **2025-12-31** 까지로 제한
+ - 그 이후 날짜는 설정 불가
+
+---
+
+### 4️⃣ 반복 일정 수정
+- 사용자가 반복 일정을 수정하려 할 때, 다음과 같은 확인 메시지를 출력한다:
+- ‘해당 일정만 수정하시겠어요?’
+- 사용자의 선택에 따라 동작이 다르다:
+- **‘예’ 선택 → 단일 일정 수정**
+ - 해당 일정만 수정됨
+ - 반복일정 아이콘 제거
+- **‘아니오’ 선택 → 전체 일정 수정**
+ - 모든 반복 일정에 수정사항 반영
+ - 반복일정 아이콘 유지
+
+---
+
+### 5️⃣ 반복 일정 삭제
+- 사용자가 반복 일정을 삭제하려 할 때, 다음과 같은 확인 메시지를 출력한다:
+- ‘해당 일정만 삭제하시겠어요?’
+- 선택에 따른 동작:
+- **‘예’ 선택 → 단일 일정 삭제**
+ - 해당 일정만 삭제됨
+- **‘아니오’ 선택 → 전체 일정 삭제**
+ - 모든 반복 일정이 삭제됨
+
+---
+
+## ⚙️ 추가 제약사항
+- 반복일정 생성 시, 기존 일정과의 중복 여부는 **검증하지 않는다.**
+- 반복 종료일(`repeatEndDate`)은 선택 사항이며, 미입력 시 기본 제한(2025-12-31)까지 반복.
+- 매월, 매년 반복 시 **유효하지 않은 날짜(예: 2월 30일)** 는 생성하지 않음.
diff --git a/.cursor/spec/stories/01-repeat-event-creation.md b/.cursor/spec/stories/01-repeat-event-creation.md
new file mode 100644
index 00000000..d6ff261e
--- /dev/null
+++ b/.cursor/spec/stories/01-repeat-event-creation.md
@@ -0,0 +1,342 @@
+# 반복 일정 생성 및 선택 기능 설계
+
+## 📋 개요
+사용자가 일정 생성 또는 수정 시 반복 유형(매일, 매주, 매월, 매년)을 선택할 수 있는 기능을 설계합니다.
+
+## 🎯 목적
+- 일정 생성/수정 폼에서 반복 유형을 선택할 수 있는 UI 제공
+- 선택된 반복 유형에 따라 적절한 데이터 구조로 저장
+- 반복 종료 조건 설정 (2025-12-31까지)
+
+## 📥 입력 (Input)
+
+### 사용자 입력
+```typescript
+interface RepeatFormInput {
+ isRepeating: boolean; // 반복 일정 여부
+ repeatType: RepeatType; // 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly'
+ repeatInterval: number; // 반복 간격 (기본값: 1)
+ repeatEndDate: string; // 반복 종료일 (YYYY-MM-DD, 최대: 2025-12-31)
+}
+```
+
+### 제약 조건
+- `repeatType`: 매일, 매주, 매월, 매년 중 하나
+- `repeatInterval`: 1 이상의 정수
+- `repeatEndDate`: 현재 날짜 이후 ~ 2025-12-31 이내
+- **매월 D일 선택**: D일이 해당 달에 존재할 때만 생성(보정 생성 금지)
+ - 예: 31일 → 31일 있는 달만, 30일 → 2월 제외, 29일 → 평년 2월 제외
+- **윤년 2월 29일에 매년 선택**: 윤년 2월 29일에만 생성
+- **평년 2월 28일에 매년 선택**: 평년에만 생성(윤년은 제외)
+- 반복 일정 생성 시 기존 일정과의 겹침(overlap) 검증을 수행하지 않음
+
+## ⚙️ 처리 로직 (Process)
+
+### 1. UI 컴포넌트 수정 (App.tsx)
+
+```typescript
+// 반복 일정 UI 활성화 (현재 주석 처리된 부분)
+{isRepeating && (
+
+
+ 반복 유형
+
+
+
+
+ 반복 간격
+ setRepeatInterval(Number(e.target.value))}
+ slotProps={{ htmlInput: { min: 1, max: 999 } }}
+ aria-label="반복 간격"
+ />
+
+
+
+ 반복 종료일
+ setRepeatEndDate(e.target.value)}
+ slotProps={{
+ htmlInput: {
+ min: date || new Date().toISOString().split('T')[0],
+ max: '2025-12-31'
+ }
+ }}
+ aria-label="반복 종료일"
+ />
+
+
+)}
+```
+
+### 2. 훅 수정 (useEventForm.ts)
+
+현재 `useEventForm` 훅은 반복 관련 상태를 이미 가지고 있으므로, `setRepeatType`, `setRepeatInterval`, `setRepeatEndDate`를 반환 객체에 추가합니다.
+
+```typescript
+// 이미 구현되어 있지만, App.tsx에서 주석 처리되어 사용 안 함
+return {
+ // ... 기존 반환값
+ setRepeatType, // 활성화 필요
+ setRepeatInterval, // 활성화 필요
+ setRepeatEndDate, // 활성화 필요
+};
+```
+
+### 3. 유효성 검증 추가
+
+새로운 유틸리티 함수 생성: `src/utils/repeatValidation.ts`
+
+```typescript
+/**
+ * 반복 종료일이 유효한지 검증
+ * @param startDate - 일정 시작일
+ * @param endDate - 반복 종료일
+ * @returns 에러 메시지 또는 null
+ */
+export function validateRepeatEndDate(
+ startDate: string,
+ endDate: string
+): string | null {
+ if (!endDate) return null;
+
+ const start = new Date(startDate);
+ const end = new Date(endDate);
+ const maxDate = new Date('2025-12-31');
+
+ if (end < start) {
+ return '반복 종료일은 시작일 이후여야 합니다.';
+ }
+
+ if (end > maxDate) {
+ return '반복 종료일은 2025-12-31 이전이어야 합니다.';
+ }
+
+ return null;
+}
+
+/**
+ * 반복 간격이 유효한지 검증
+ */
+export function validateRepeatInterval(interval: number): string | null {
+ if (interval < 1) {
+ return '반복 간격은 1 이상이어야 합니다.';
+ }
+ if (interval > 999) {
+ return '반복 간격은 999 이하여야 합니다.';
+ }
+ return null;
+}
+```
+
+## 📤 출력 (Output)
+
+### 저장되는 데이터 구조
+```typescript
+interface Event {
+ id: string;
+ title: string;
+ date: string;
+ startTime: string;
+ endTime: string;
+ description: string;
+ location: string;
+ category: string;
+ repeat: {
+ type: RepeatType; // 'daily' | 'weekly' | 'monthly' | 'yearly'
+ interval: number; // 1, 2, 3, ...
+ endDate?: string; // 'YYYY-MM-DD' 또는 undefined
+ id?: string;
+ };
+ notificationTime: number;
+}
+```
+
+### API 요청 형식
+```typescript
+// 일반 일정 단건 생성
+// POST /api/events
+{
+ "title": "회의",
+ "date": "2025-01-01",
+ "startTime": "10:00",
+ "endTime": "11:00",
+ "description": "정기 회의",
+ "location": "회의실 A",
+ "category": "업무",
+ "repeat": { "type": "none", "interval": 1 },
+ "notificationTime": 10
+}
+
+// 반복 일정 생성(저장 시 인스턴스 배열 생성 후 전송)
+// POST /api/events-list
+{
+ "events": [
+ {
+ "title": "주간 회의",
+ "date": "2025-01-06",
+ "startTime": "10:00",
+ "endTime": "11:00",
+ "description": "정기 주간 회의",
+ "location": "회의실 A",
+ "category": "업무",
+ "repeat": { "type": "weekly", "interval": 1, "endDate": "2025-12-31" },
+ "notificationTime": 10
+ }
+ // ... generateRepeatEvents로 생성된 다른 날짜들
+ ]
+}
+
+// 서버는 저장 시 각 이벤트에 id를 부여하고,
+// 반복 일정인 경우 repeat.id(시리즈 식별자)를 공통으로 부여함
+```
+
+## 📁 영향받는 파일
+
+### 수정 필요
+1. ✅ `src/types.ts` - `RepeatInfo.id?` 필드 추가
+2. 🔧 `src/App.tsx` - 주석 해제 및 UI 활성화
+3. 🔧 `src/hooks/useEventForm.ts` - setter 함수 반환 활성화
+4. ✨ `src/utils/repeatValidation.ts` - 새로 생성
+
+### 테스트 파일 추가
+5. ✨ `src/__tests__/unit/easy.repeatValidation.spec.ts` - 새로 생성
+
+## 🚨 고려사항
+
+### 특수 케이스 처리
+
+#### 1. 매월 31일 선택
+```typescript
+// ❌ 잘못된 처리: 매월 마지막 날
+// ✅ 올바른 처리: 31일이 있는 달에만 생성 (1, 3, 5, 7, 8, 10, 12월)
+
+// 예시: 2025-01-31에 매월 반복 설정
+// → 생성: 1/31, 3/31, 5/31, 7/31, 8/31, 10/31, 12/31
+// → 생략: 2월, 4월, 6월, 9월, 11월
+```
+
+#### 2. 윤년 2월 29일 선택
+```typescript
+// 예시: 2024-02-29에 매년 반복 설정
+// → 생성: 윤년에만 (2024-02-29)
+// → 생략: 평년 (2025년은 생략)
+```
+
+#### 3. 반복 종료일 제한
+```typescript
+// 최대 반복 종료일: 2025-12-31
+// 이후 날짜는 입력 불가 (HTML input max 속성 활용)
+```
+
+### UI/UX 고려사항
+- 반복 일정 체크박스를 해제하면 반복 관련 필드 숨김
+- 반복 간격 입력은 양의 정수만 허용
+- 반복 종료일은 필수가 아님 (선택 사항)
+- 날짜 선택기의 min/max 속성으로 유효하지 않은 날짜 입력 방지
+
+### 성능/저장 전략
+- 반복 일정은 저장 시점에 클라이언트에서 인스턴스 배열을 생성한 뒤 `/api/events-list`로 일괄 저장
+- 서버는 각 인스턴스에 `id`를, 반복 시리즈에는 공통 `repeat.id`를 부여
+- 조회 시에는 저장된 인스턴스를 그대로 사용(동적 계산 없음)
+- 2025-12-31까지 최대 생성 가능한 일정 수:
+ - 매일: 최대 365개
+ - 매주: 최대 52개
+ - 매월: 최대 12개
+ - 매년: 1개
+
+## ✅ 검증 방법
+
+### 단위 테스트
+```typescript
+describe('반복 일정 유효성 검증', () => {
+ test('반복 종료일이 시작일보다 이전이면 에러', () => {
+ const error = validateRepeatEndDate('2025-12-31', '2025-01-01');
+ expect(error).toBe('반복 종료일은 시작일 이후여야 합니다.');
+ });
+
+ test('반복 종료일이 2025-12-31 이후면 에러', () => {
+ const error = validateRepeatEndDate('2025-01-01', '2026-01-01');
+ expect(error).toBe('반복 종료일은 2025-12-31 이전이어야 합니다.');
+ });
+
+ test('반복 간격이 1 미만이면 에러', () => {
+ const error = validateRepeatInterval(0);
+ expect(error).toBe('반복 간격은 1 이상이어야 합니다.');
+ });
+});
+```
+
+### 통합 테스트
+- 반복 일정 체크박스 선택 시 관련 필드 표시 확인
+- 각 반복 유형 선택 가능 확인
+- 유효하지 않은 값 입력 시 에러 메시지 표시 확인
+
+## 🔗 관련 기능
+- [반복 일정 생성 로직](./05-repeat-event-generation.md)
+- [반복 일정 표시](./02-repeat-event-display.md)
+- [반복 일정 수정](./03-repeat-event-update.md)
+
+
+## 🧭 개발 원칙과 TDD 사이클 지침
+
+### 변경 최소화 원칙
+- **기존 코드 최대한 유지**: 기존 함수 시그니처, 파일 경로, 공용 타입은 가급적 변경하지 않습니다.
+- **관심사 분리 준수**: 새 요구사항은 가능하면 새 파일로 추가합니다.
+ - 유틸: `src/utils/`
+ - 훅: `src/hooks/`
+ - 테스트: `src/__tests__/`
+- **허용되는 변경의 예**
+ - 기존 훅의 반환 객체에 setter 등 필드 "추가" (삭제/이름변경 금지)
+ - UI 활성화를 위한 주석 해제 및 접근성 속성 추가
+ - 타입의 선택 필드 "추가" (기존 필드 변경/삭제 금지)
+
+### TDD 사이클
+1) Red: 실패 테스트 먼저 작성
+- 새로운 요구사항을 드러내는 테스트를 선행합니다.
+- 이 단계에서는 구현을 최소한으로 유지하며, 테스트가 실패하는지 확인합니다.
+
+2) Green: 최소 구현으로 통과
+- 기존 코드를 광범위하게 리팩터링하지 말고, "추가" 방식으로 통과시킵니다.
+- 새 유틸/훅/타입을 추가하고, 필요한 지점에만 최소 연결을 합니다.
+
+3) Refactor: 중복 제거와 정리
+- 테스트가 모두 초록이 된 후, 코드 품질을 개선합니다.
+- 이 단계에서 린트 예외를 제거하고, 주석/네이밍/성능을 손봅니다.
+
+### 실패 테스트 단계의 린트 예외
+- Red 단계(실패 테스트 작성)에서는 일시적으로 **ESLint 경고를 무시**할 수 있습니다.
+- 테스트 파일 범위에서 필요한 규칙만 제한적으로 비활성화하세요. Green 단계에서 반드시 제거합니다.
+
+```ts
+/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */
+// Red 단계: 실패를 확인하기 위한 임시 린트 예외. Green/Refactor 단계에서 제거 필수.
+```
+
+### 실행 체크리스트
+- Red: 테스트 추가 → 실패 확인
+- Green: 최소 구현 → 테스트 통과 확인
+- Refactor: 린트 재활성화 → `pnpm lint` / `pnpm test` / `pnpm build` 모두 통과
+
+### 로컬 확인 명령어
+- 애플리케이션 실행: `pnpm start`
+- 접속 URL: `http://localhost:5173`
diff --git a/.cursor/spec/stories/02-repeat-event-display.md b/.cursor/spec/stories/02-repeat-event-display.md
new file mode 100644
index 00000000..33f69e89
--- /dev/null
+++ b/.cursor/spec/stories/02-repeat-event-display.md
@@ -0,0 +1,51 @@
+---
+title: 반복 일정 표시 스토리
+category: calendar
+type: story
+updated: 2025-10-30
+---
+
+## 목적
+반복 일정은 캘린더 뷰와 일정 리스트에서 일반 일정과 시각적으로 구분되어야 한다. 접근성(ARIA) 속성을 포함하여 반복 여부를 명확히 표현한다.
+
+## 사용자 시나리오
+1. 사용자가 반복 일정(예: 매주)을 생성한다.
+2. 캘린더 주/월 뷰의 해당 날짜 셀에 반복 아이콘이 노출된다.
+3. 우측 일정 리스트에도 같은 반복 아이콘이 노출된다.
+4. 알림이 트리거된 일정의 경우 알림 아이콘과 함께 표기되며, 아이콘 우선순위는 알림 > 반복이다.
+
+## 표시 규칙
+- 반복 여부: `event.repeat.type !== 'none'`이면 반복으로 간주
+- 아이콘: Material-UI `Repeat` 아이콘 사용
+- 접근성: 아이콘 요소에 `aria-label="반복 일정"` 부여
+- 위치
+ - 주 뷰, 월 뷰: 각 일정 배지 내부의 텍스트 좌측 아이콘 Stack에 반복 아이콘 추가
+ - 일정 리스트: 제목 좌측 아이콘 Stack에 반복 아이콘 추가
+- 아이콘 우선순위: 알림 아이콘(`Notifications`) 다음에 반복 아이콘(`Repeat`) 순서로 렌더링
+
+## 예외/비고
+- 반복 여부는 표시만 하며, 인스턴스 생성 로직과 겹침 검증은 본 스토리 범위가 아님
+- 색상은 테마 기본을 사용하고 하드코딩 금지
+
+## 접근성
+- `aria-label="반복 일정"` 필수
+- 시맨틱 마크업 유지, 아이콘만으로 의미가 전달되지 않도록 텍스트 정보 병행 표기(제목 등)
+
+## 테스트 케이스 (TDD)
+1) 실패 테스트: 주/월 뷰에 반복 아이콘 노출 검증
+ - 준비: 서버에서 이벤트 로드, 하나는 `repeat.type !== 'none'`
+ - 기대: 해당 날짜 셀 내에서 `getByLabelText('반복 일정')` 탐지됨
+
+2) 실패 테스트: 일정 리스트에 반복 아이콘 노출 검증
+ - 준비: 동일 데이터
+ - 기대: 리스트 항목 내 `getAllByLabelText('반복 일정')` 중 최소 1개 존재
+
+3) 우선순위 테스트: 알림 아이콘이 있을 때 반복 아이콘과 함께 노출되며, 순서가 알림 > 반복
+ - 준비: `notifiedEvents`에 대상 이벤트 id 포함
+ - 기대: DOM 순서상 Notifications 다음 Repeat가 배치됨
+
+4) 접근성 테스트: 반복 아이콘에 `aria-label`이 정확히 부여됨
+
+## Out of Scope
+- 반복 인스턴스 생성/검증 규칙(요구사항 1, 3)은 별도 스토리/테스트에서 다룸
+
diff --git a/.cursor/spec/stories/03-repeat-end-condition.md b/.cursor/spec/stories/03-repeat-end-condition.md
new file mode 100644
index 00000000..068ba805
--- /dev/null
+++ b/.cursor/spec/stories/03-repeat-end-condition.md
@@ -0,0 +1,31 @@
+---
+title: 반복 종료 조건 스토리
+category: calendar
+type: story
+updated: 2025-10-30
+---
+
+## 목적
+사용자가 반복 종료일을 설정할 수 있으며, 미입력 시 시스템이 기본 종료일(2025-12-31)을 적용한다.
+
+## 사용자 시나리오
+1. 사용자가 반복 일정을 생성하면서 종료일을 비워둔다.
+2. 시스템은 기본 종료일 `2025-12-31`까지로 반복을 제한하여 인스턴스를 생성한다.
+3. 종료일을 입력한 경우 해당 날짜(포함)까지만 생성한다.
+
+## 규칙
+- 종료일 입력이 없으면 기본값 `2025-12-31` 적용
+- 종료일이 있으면 해당 값 사용(별도 검증은 `repeatValidation`에서 수행)
+- UI 제약: 종료일 입력의 `max` 속성은 `2025-12-31`
+
+## 접근성
+- 폼 입력에 `id`/레이블 제공(기존 규칙 유지)
+
+## 테스트 케이스 (TDD)
+1) 실패 테스트: 유틸 `getEffectiveEndDate`가 종료일 미입력 시 `2025-12-31`을 반환
+2) 실패 테스트: 종료일 입력 시 해당 값을 그대로 반환
+3) 통합: `generateRepeatEvents`가 기본 종료일을 적용해 무한 생성이 발생하지 않음(월/년 반복)
+
+## Out of Scope
+- 종료일 형식·범위 검증은 `repeatValidation`에서 커버
+
diff --git a/.cursor/spec/stories/03-repeat-event-update.md b/.cursor/spec/stories/03-repeat-event-update.md
new file mode 100644
index 00000000..ce0b8067
--- /dev/null
+++ b/.cursor/spec/stories/03-repeat-event-update.md
@@ -0,0 +1,401 @@
+# 반복 일정 수정 기능 설계
+
+## 📋 개요
+반복 일정을 수정할 때 "해당 일정만 수정" 또는 "전체 수정"을 선택할 수 있는 기능을 설계합니다.
+
+## 🎯 목적
+- 사용자가 반복 일정의 한 인스턴스를 수정할 때 선택권 제공
+- 단일 수정: 해당 일정만 변경, 반복 일정에서 분리
+- 전체 수정: 모든 반복 일정에 변경사항 적용
+
+## 📥 입력 (Input)
+
+### 사용자 입력
+```typescript
+interface UpdateRepeatEventInput {
+ eventId: string; // 수정할 이벤트 ID
+ updateType: 'single' | 'all'; // 수정 범위
+ updatedData: Partial; // 변경할 데이터
+}
+```
+
+### 수정 시나리오
+1. **단일 수정 ('예' 선택)**
+ - 해당 일정만 수정
+ - 반복 일정에서 분리 (repeat.type을 'none'으로 변경)
+ - 반복 아이콘 사라짐
+ - 원본 반복 일정은 유지
+
+2. **전체 수정 ('아니오' 선택)**
+ - 모든 반복 일정에 변경사항 적용
+ - 반복 설정 유지
+ - 반복 아이콘 유지
+
+## ⚙️ 처리 로직 (Process)
+
+### 1. 타입 정의 확장 (types.ts)
+
+```typescript
+// 반복 시리즈 식별자는 서버에서 repeat.id로 부여됨
+export interface RepeatInfo {
+ type: RepeatType;
+ interval: number;
+ endDate?: string;
+ id?: string; // 선택 필드, 시리즈 전체 수정/삭제에 사용
+}
+
+export type RepeatUpdateType = 'single' | 'all';
+```
+
+### 2. 확인 다이얼로그 추가 (App.tsx)
+
+```typescript
+// 상태 추가
+const [isRepeatUpdateDialogOpen, setIsRepeatUpdateDialogOpen] = useState(false);
+const [pendingUpdate, setPendingUpdate] = useState<{
+ event: Event;
+ updateData: Partial;
+} | null>(null);
+
+// 수정 버튼 클릭 핸들러
+const handleEditClick = (event: Event) => {
+ // 반복 일정인지 확인
+ if (event.repeat.type !== 'none') {
+ // 폼에 데이터 채우기
+ editEvent(event);
+
+ // 확인 다이얼로그 표시 (수정 완료 버튼 클릭 시)
+ // 이 로직은 addOrUpdateEvent 함수 내부로 이동
+ } else {
+ // 일반 일정은 바로 수정 모드
+ editEvent(event);
+ }
+};
+
+// 일정 저장 로직 수정
+const addOrUpdateEvent = async () => {
+ // ... 유효성 검증
+
+ const eventData: Event | EventForm = {
+ // ... 데이터 구성
+ };
+
+ // 반복 일정 수정인 경우
+ if (editingEvent && editingEvent.repeat.type !== 'none') {
+ setPendingUpdate({ event: editingEvent, updateData: eventData });
+ setIsRepeatUpdateDialogOpen(true);
+ return;
+ }
+
+ // 일반 저장 로직
+ const overlapping = findOverlappingEvents(eventData, events);
+ if (overlapping.length > 0) {
+ setOverlappingEvents(overlapping);
+ setIsOverlapDialogOpen(true);
+ } else {
+ await saveEvent(eventData);
+ resetForm();
+ }
+};
+
+// 반복 일정 수정 확인 다이얼로그
+
+```
+
+### 3. 수정 처리 함수
+
+```typescript
+const handleRepeatUpdate = async (updateType: RepeatUpdateType) => {
+ if (!pendingUpdate) return;
+
+ const { event, updateData } = pendingUpdate;
+
+ if (updateType === 'single') {
+ // 단일 수정: 반복 정보 제거
+ const updatedEvent = {
+ ...updateData,
+ id: event.id,
+ repeat: {
+ type: 'none' as RepeatType,
+ interval: 1,
+ },
+ // 시리즈에서 분리: repeat.id는 서버 저장 후 제거됨(선택)
+ };
+
+ await saveEvent(updatedEvent);
+ } else {
+ // 전체 수정: 반복 정보 유지
+ const updatedEvent = {
+ ...updateData,
+ id: event.id,
+ repeat: event.repeat, // 기존 반복 정보 유지
+ };
+
+ await updateRepeatSeries(event.repeat.id!, updatedEvent);
+ }
+
+ setIsRepeatUpdateDialogOpen(false);
+ setPendingUpdate(null);
+ resetForm();
+};
+```
+
+### 4. 반복 시리즈 전체 수정 (useEventOperations.ts)
+
+```typescript
+/**
+ * 반복 시리즈의 모든 일정 업데이트
+ */
+const updateRepeatSeries = async (repeatId: string, updateData: Partial) => {
+ try {
+ // 서버에 전체 수정 요청 (기존 엔드포인트 사용)
+ const response = await fetch(`/api/recurring-events/${repeatId}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(updateData),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to update repeat group');
+ }
+
+ await fetchEvents();
+ enqueueSnackbar('반복 일정이 모두 수정되었습니다.', { variant: 'success' });
+ } catch (error) {
+ console.error('Error updating repeat series:', error);
+ enqueueSnackbar('반복 일정 수정 실패', { variant: 'error' });
+ }
+};
+
+// 반환 객체에 추가
+return {
+ events,
+ fetchEvents,
+ saveEvent,
+ deleteEvent,
+ updateRepeatSeries // 새로 추가
+};
+```
+
+### 5. 서버 API 엔드포인트
+
+기존 서버의 반복 시리즈 엔드포인트를 사용합니다.
+
+```http
+PUT /api/recurring-events/:repeatId // 시리즈 전체 수정
+```
+
+## 📤 출력 (Output)
+
+### 단일 수정 결과
+```typescript
+// Before (반복 일정)
+{
+ id: "event-1-20250106",
+ title: "주간 회의",
+ date: "2025-01-06",
+ repeat: {
+ type: "weekly",
+ interval: 1,
+ endDate: "2025-12-31",
+ id: "series-1"
+ }
+}
+
+// After (단일 일정으로 변경)
+{
+ id: "event-1-20250106",
+ title: "임시 회의", // 제목 변경
+ date: "2025-01-06",
+ repeat: {
+ type: "none", // 반복 제거
+ interval: 1
+ }
+}
+
+// 다른 반복 일정들은 유지
+// event-1-20250113, event-1-20250120, ... 계속 존재
+```
+
+### 전체 수정 결과
+```typescript
+// Before
+{
+ id: "event-1-20250106",
+ title: "주간 회의",
+ startTime: "10:00",
+ repeat: { type: "weekly", interval: 1, id: "series-1" }
+}
+
+// After (그룹의 모든 이벤트)
+{
+ id: "event-1-20250106",
+ title: "팀 스탠드업", // 제목 변경
+ startTime: "09:00", // 시간 변경
+ repeat: { type: "weekly", interval: 1, id: "series-1" } // 반복 유지
+}
+// event-1-20250113, event-1-20250120도 모두 동일하게 변경
+```
+
+## 📁 영향받는 파일
+
+### 수정 필요
+1. 🔧 `src/types.ts` - `RepeatInfo.id?` 필드 추가(완료)
+2. 🔧 `src/App.tsx` - 확인 다이얼로그 및 수정 로직 추가
+3. 🔧 `src/hooks/useEventOperations.ts` - `updateRepeatSeries` 함수 추가
+4. 🔧 서버 엔드포인트 변경 없음(기존 `/api/recurring-events/:repeatId` 사용)
+
+### 테스트 파일 추가
+5. 🔧 `src/__tests__/hooks/medium.useEventOperations.spec.ts` - 테스트 추가
+6. ✨ `src/__tests__/integration/repeat-update.spec.tsx` - 통합 테스트
+
+## 🚨 고려사항
+
+### 데이터 무결성
+
+#### 반복 시리즈 식별자 관리
+```typescript
+// 반복 일정 저장 시 서버가 공통 시리즈 식별자를 repeat.id로 부여
+// 클라이언트는 repeat.id를 사용해 전체 수정/삭제 요청을 수행
+```
+
+#### 단일 수정 후 원본 추적
+```typescript
+// originalDate를 사용하여 원본 반복 일정 추적 가능
+// 필요 시 "원래 반복 일정으로 되돌리기" 기능 구현 가능
+```
+
+### UI/UX 고려사항
+
+#### 다이얼로그 문구
+```
+제목: "반복 일정 수정"
+내용: "해당 일정만 수정하시겠어요?"
+버튼:
+ - "예 (해당 일정만)" → 단일 수정
+ - "아니오 (전체 수정)" → 전체 수정
+ - "취소" (X 버튼)
+```
+
+#### 수정 후 피드백
+- 단일 수정: "일정이 수정되었습니다."
+- 전체 수정: "반복 일정 N개가 모두 수정되었습니다."
+
+#### 수정 제한사항
+```typescript
+// 전체 수정 시 반복 설정 자체는 수정 가능
+// 하지만 반복 유형 변경은 주의 필요 (예: 매주 → 매월)
+
+// 안전한 방법: 전체 수정 시 반복 설정은 변경 불가
+// 변경하려면 삭제 후 재생성 권장
+```
+
+### 성능 고려사항
+
+#### 전체 수정 시 트랜잭션
+```text
+// 서버는 /api/recurring-events/:repeatId 엔드포인트에서 일괄 업데이트를 처리
+// 일부 실패 시 적절한 에러 응답을 반환(서버 구현 범위)
+```
+
+#### 대량 업데이트 최적화
+```typescript
+// 반복 일정이 많을 경우 (100개 이상)
+// 서버 측에서 배치 업데이트 최적화 필요
+// 클라이언트는 로딩 상태 표시
+```
+
+### 예외 상황 처리
+
+#### 1. 반복 일정의 일부가 이미 단일 수정된 경우
+```typescript
+// 전체 수정은 동일한 repeat.id를 가진 일정에만 적용
+// 이미 단일 수정되어 repeat.type === 'none'인 일정은 영향 없음
+```
+
+#### 2. 수정 중 네트워크 오류
+```typescript
+// 재시도 로직 또는 사용자에게 명확한 오류 메시지
+enqueueSnackbar('네트워크 오류로 수정에 실패했습니다. 다시 시도해주세요.', {
+ variant: 'error'
+});
+```
+
+## ✅ 검증 방법
+
+### 단위 테스트
+```typescript
+describe('반복 일정 수정', () => {
+ test('단일 수정 시 repeat.type이 none으로 변경', async () => {
+ const event: Event = {
+ id: '1',
+ repeat: { type: 'weekly', interval: 1, id: 'series-1' },
+ // ...
+ };
+
+ const result = await updateSingleEvent(event, { title: 'New Title' });
+ expect(result.repeat.type).toBe('none');
+ expect(result.repeat.type).toBe('none');
+ });
+
+ test('전체 수정 시 모든 시리즈 이벤트가 업데이트', async () => {
+ const repeatId = 'series-1';
+ const updateData = { title: 'Updated Title' };
+
+ await updateRepeatSeries(repeatId, updateData);
+
+ const seriesEvents = events.filter(e => e.repeat.id === repeatId);
+ seriesEvents.forEach(event => {
+ expect(event.title).toBe('Updated Title');
+ expect(event.repeat.type).not.toBe('none');
+ });
+ });
+});
+```
+
+### 통합 테스트
+```typescript
+test('반복 일정 수정 플로우', async () => {
+ // 1. 반복 일정 생성
+ // 2. 수정 버튼 클릭
+ // 3. 확인 다이얼로그 표시 확인
+ // 4. "예" 클릭 → 단일 수정
+ // 5. 반복 아이콘 사라짐 확인
+
+ // 6. 다른 반복 일정 수정
+ // 7. "아니오" 클릭 → 전체 수정
+ // 8. 모든 반복 일정 변경 확인
+ // 9. 반복 아이콘 유지 확인
+});
+```
+
+## 🔗 관련 기능
+- [반복 일정 생성](./01-repeat-event-creation.md)
+- [반복 일정 삭제](./04-repeat-event-delete.md)
+- [반복 일정 생성 로직](./05-repeat-event-generation.md)
+
diff --git a/.cursor/spec/stories/04-repeat-event-delete.md b/.cursor/spec/stories/04-repeat-event-delete.md
new file mode 100644
index 00000000..79ea81a8
--- /dev/null
+++ b/.cursor/spec/stories/04-repeat-event-delete.md
@@ -0,0 +1,489 @@
+# 반복 일정 삭제 기능 설계
+
+## 📋 개요
+반복 일정을 삭제할 때 "해당 일정만 삭제" 또는 "전체 삭제"를 선택할 수 있는 기능을 설계합니다.
+
+## 🎯 목적
+- 사용자가 반복 일정의 한 인스턴스를 삭제할 때 선택권 제공
+- 단일 삭제: 해당 일정만 삭제
+- 전체 삭제: 모든 반복 일정 삭제
+
+## 📥 입력 (Input)
+
+### 사용자 입력
+```typescript
+interface DeleteRepeatEventInput {
+ eventId: string; // 삭제할 이벤트 ID
+ deleteType: 'single' | 'all'; // 삭제 범위
+ repeatId?: string; // 반복 시리즈 ID (전체 삭제 시 필요, repeat.id)
+}
+```
+
+### 삭제 시나리오
+1. **단일 삭제 ('예' 선택)**
+ - 해당 일정만 삭제
+ - 다른 반복 일정은 유지
+
+2. **전체 삭제 ('아니오' 선택)**
+ - 같은 반복 시리즈의 모든 일정 삭제
+ - 동일한 `repeat.id`를 가진 모든 이벤트 제거
+
+## ⚙️ 처리 로직 (Process)
+
+### 1. 확인 다이얼로그 추가 (App.tsx)
+
+```typescript
+// 상태 추가
+const [isRepeatDeleteDialogOpen, setIsRepeatDeleteDialogOpen] = useState(false);
+const [pendingDelete, setPendingDelete] = useState(null);
+
+// 삭제 버튼 클릭 핸들러 수정
+const handleDeleteClick = (event: Event) => {
+ // 반복 일정인지 확인
+ if (event.repeat.type !== 'none') {
+ setPendingDelete(event);
+ setIsRepeatDeleteDialogOpen(true);
+ } else {
+ // 일반 일정은 바로 삭제
+ deleteEvent(event.id);
+ }
+};
+
+// 기존 삭제 버튼 onClick 수정
+ handleDeleteClick(event)} // 수정
+>
+
+
+
+// 반복 일정 삭제 확인 다이얼로그
+
+```
+
+### 2. 삭제 처리 함수 (App.tsx)
+
+```typescript
+/**
+ * 반복 일정 삭제 처리
+ */
+const handleRepeatDelete = async (deleteType: 'single' | 'all') => {
+ if (!pendingDelete) return;
+
+ if (deleteType === 'single') {
+ // 단일 삭제: 해당 일정만 제거
+ await deleteEvent(pendingDelete.id);
+ } else {
+ // 전체 삭제: 반복 시리즈 전체 제거
+ await deleteRepeatSeries(pendingDelete.repeat.id);
+ }
+
+ setIsRepeatDeleteDialogOpen(false);
+ setPendingDelete(null);
+};
+```
+
+### 3. 반복 시리즈 전체 삭제 (useEventOperations.ts)
+
+```typescript
+/**
+ * 반복 시리즈의 모든 일정 삭제
+ */
+const deleteRepeatSeries = async (repeatId: string) => {
+ try {
+ const response = await fetch(`/api/recurring-events/${repeatId}`, { method: 'DELETE' });
+
+ if (!response.ok) {
+ throw new Error('Failed to delete repeat group');
+ }
+
+ await fetchEvents();
+ enqueueSnackbar('반복 일정이 모두 삭제되었습니다.', { variant: 'info' });
+ } catch (error) {
+ console.error('Error deleting repeat series:', error);
+ enqueueSnackbar('반복 일정 삭제 실패', { variant: 'error' });
+ }
+};
+
+// 반환 객체에 추가
+return {
+ events,
+ fetchEvents,
+ saveEvent,
+ deleteEvent,
+ deleteRepeatSeries // 새로 추가
+};
+```
+
+### 4. 서버 API 엔드포인트 (변경 없음)
+
+기존 서버의 반복 시리즈 엔드포인트를 사용합니다.
+
+```http
+DELETE /api/recurring-events/:repeatId // 시리즈 전체 삭제
+```
+
+### 5. Hook 확장 (useEventOperations.ts)
+
+```typescript
+export const useEventOperations = (editing: boolean, onSave?: () => void) => {
+ const [events, setEvents] = useState([]);
+ const { enqueueSnackbar } = useSnackbar();
+
+ // ... 기존 함수들
+
+ /**
+ * 반복 그룹 전체 삭제
+ */
+ const deleteRepeatSeries = async (repeatId?: string) => {
+ if (!repeatId) {
+ enqueueSnackbar('반복 그룹 ID가 없습니다.', { variant: 'error' });
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/recurring-events/${repeatId}`, { method: 'DELETE' });
+
+ if (!response.ok) {
+ throw new Error('Failed to delete repeat group');
+ }
+
+ await fetchEvents();
+ enqueueSnackbar('반복 일정이 삭제되었습니다.', { variant: 'info' });
+ } catch (error) {
+ console.error('Error deleting repeat series:', error);
+ enqueueSnackbar('반복 일정 삭제 실패', { variant: 'error' });
+ }
+ };
+
+ return {
+ events,
+ fetchEvents,
+ saveEvent,
+ deleteEvent,
+ deleteRepeatSeries
+ };
+};
+```
+
+## 📤 출력 (Output)
+
+### 단일 삭제 결과
+```typescript
+// Before: 3개의 반복 일정
+[
+ { id: "1", date: "2025-01-06", repeat: { type: "weekly", interval: 1, id: "series-1" } },
+ { id: "2", date: "2025-01-13", repeat: { type: "weekly", interval: 1, id: "series-1" } },
+ { id: "3", date: "2025-01-20", repeat: { type: "weekly", interval: 1, id: "series-1" } },
+]
+
+// After: id="2"만 삭제
+[
+ { id: "1", date: "2025-01-06", repeat: { type: "weekly", interval: 1, id: "series-1" } }, // 유지
+ // id="2" 삭제됨
+ { id: "3", date: "2025-01-20", repeat: { type: "weekly", interval: 1, id: "series-1" } }, // 유지
+]
+```
+
+### 전체 삭제 결과
+```typescript
+// Before: 여러 반복 일정과 일반 일정
+[
+ { id: "1", date: "2025-01-06", repeat: { type: "weekly", interval: 1, id: "series-1" } },
+ { id: "2", date: "2025-01-13", repeat: { type: "weekly", interval: 1, id: "series-1" } },
+ { id: "3", date: "2025-01-20", repeat: { type: "weekly", interval: 1, id: "series-1" } },
+ { id: "4", date: "2025-01-15", repeat: { type: "none", interval: 1 } }, // 일반 일정
+]
+
+// After: repeat.id="series-1" 모두 삭제
+[
+ { id: "4", date: "2025-01-15", repeat: { type: "none" } }, // 유지
+]
+```
+
+### API 응답
+```typescript
+// 단일 삭제
+{
+ "message": "Event deleted"
+}
+
+// 전체 삭제
+{
+ "message": "Repeat group deleted",
+ "deletedCount": 10
+}
+```
+
+### 사용자 피드백
+```typescript
+// 단일 삭제
+"일정이 삭제되었습니다." (info)
+
+// 전체 삭제
+"반복 일정 10개가 삭제되었습니다." (info)
+
+// 오류
+"반복 일정 삭제 실패" (error)
+```
+
+## 📁 영향받는 파일
+
+### 수정 필요
+1. 🔧 `src/App.tsx` - 확인 다이얼로그 및 삭제 로직 추가
+2. 🔧 `src/hooks/useEventOperations.ts` - `deleteRepeatSeries` 함수 추가
+3. 🔧 서버 엔드포인트 변경 없음(기존 `/api/recurring-events/:repeatId` 사용)
+
+### 테스트 파일 추가
+4. 🔧 `src/__tests__/hooks/medium.useEventOperations.spec.ts` - 테스트 추가
+5. ✨ `src/__tests__/integration/repeat-delete.spec.tsx` - 통합 테스트
+
+## 🚨 고려사항
+
+### 안전성
+
+#### 실수 방지
+```typescript
+// 다이얼로그 문구를 명확하게
+// "예" = 해당 일정만, "아니오" = 전체
+// 위험한 작업(전체 삭제)은 빨간색으로 강조
+
+
+
+```
+
+#### 되돌리기 불가능 경고
+```typescript
+// 전체 삭제 시 추가 경고 (선택사항)
+
+ 해당 일정만 삭제하시겠어요?
+
+ ※ 전체 삭제 시 모든 반복 일정이 영구적으로 삭제되며 복구할 수 없습니다.
+
+
+```
+
+### UI/UX 고려사항
+
+#### 다이얼로그 디자인
+```
+┌────────────────────────────────┐
+│ 반복 일정 삭제 [X]│
+├────────────────────────────────┤
+│ 해당 일정만 삭제하시겠어요? │
+│ │
+│ ※ 전체 삭제 시 N개의 반복 │
+│ 일정이 모두 삭제됩니다. │
+├────────────────────────────────┤
+│ [취소] [아니오-전체] [예-단일]│
+└────────────────────────────────┘
+```
+
+#### 삭제된 개수 표시
+```typescript
+// 전체 삭제 후 피드백에 삭제된 개수 포함
+enqueueSnackbar(
+ `반복 일정 ${deletedCount}개가 삭제되었습니다.`,
+ { variant: 'info' }
+);
+```
+
+#### 로딩 상태
+```typescript
+// 대량 삭제 시 로딩 표시
+const [isDeleting, setIsDeleting] = useState(false);
+
+const deleteRepeatSeries = async (repeatId) => {
+ setIsDeleting(true);
+ try {
+ // ... 삭제 로직
+ } finally {
+ setIsDeleting(false);
+ }
+};
+```
+
+### 성능 고려사항
+
+#### 대량 삭제 최적화
+```javascript
+// 서버에서 배치 삭제
+app.delete('/api/recurring-events/:repeatId', (req, res) => {
+ const { repeatId } = req.params;
+
+ // filter를 한 번만 실행
+ const newEvents = events.filter(e => e.repeat?.id !== repeatId);
+ const deletedCount = events.length - newEvents.length;
+ events = newEvents;
+
+ res.json({ deletedCount });
+});
+```
+
+#### 클라이언트 캐시 업데이트
+```typescript
+// 삭제 후 전체 목록 재조회 vs. 로컬 상태 업데이트
+// 현재: fetchEvents() 호출 (안전하지만 느림)
+// 최적화: 로컬 상태에서 직접 제거 (빠르지만 동기화 주의)
+
+// 최적화 예시
+const deleteRepeatSeries = async (repeatId) => {
+ // 서버 요청
+ await fetch(`/api/recurring-events/${repeatId}`, { method: 'DELETE' });
+
+ // 로컬 상태 업데이트 (재조회 없이)
+ setEvents(prev => prev.filter(e => e.repeat?.id !== repeatId));
+};
+```
+
+### 예외 상황 처리
+
+#### 1. repeat.id가 없는 경우
+```typescript
+if (!pendingDelete.repeat?.id) {
+ enqueueSnackbar('반복 그룹 정보가 없습니다.', { variant: 'error' });
+ return;
+}
+```
+
+#### 2. 이미 삭제된 일정
+```typescript
+// 서버에서 404 처리
+if (groupEvents.length === 0) {
+ return res.status(404).json({ error: 'Repeat group not found' });
+}
+
+// 클라이언트에서 처리
+if (!response.ok) {
+ if (response.status === 404) {
+ enqueueSnackbar('이미 삭제된 일정입니다.', { variant: 'warning' });
+ }
+}
+```
+
+#### 3. 네트워크 오류
+```typescript
+try {
+ await deleteRepeatSeries(repeatId);
+} catch (error) {
+ enqueueSnackbar(
+ '네트워크 오류로 삭제에 실패했습니다. 다시 시도해주세요.',
+ { variant: 'error' }
+ );
+}
+```
+
+### 알림 삭제 처리
+
+```typescript
+// 반복 일정 삭제 시 관련 알림도 제거
+// useNotifications 훅에서 처리 필요
+useEffect(() => {
+ // 존재하지 않는 이벤트의 알림 제거
+ setNotifications(prev =>
+ prev.filter(notif =>
+ events.some(event => event.id === notif.id)
+ )
+ );
+}, [events]);
+```
+
+## ✅ 검증 방법
+
+### 단위 테스트
+```typescript
+describe('반복 일정 삭제', () => {
+ test('단일 삭제 시 해당 일정만 제거', async () => {
+ const events = [
+ { id: '1', repeat: { id: 'series-1' } },
+ { id: '2', repeat: { id: 'series-1' } },
+ { id: '3', repeat: { id: 'series-1' } },
+ ];
+
+ await deleteEvent('2');
+
+ const remaining = getEvents();
+ expect(remaining).toHaveLength(2);
+ expect(remaining.find(e => e.id === '2')).toBeUndefined();
+ });
+
+ test('전체 삭제 시 그룹의 모든 일정 제거', async () => {
+ const events = [
+ { id: '1', repeat: { id: 'series-1' } },
+ { id: '2', repeat: { id: 'series-1' } },
+ { id: '3', repeat: { id: 'series-2' } },
+ ];
+
+ const result = await deleteRepeatSeries('series-1');
+
+ expect(result.deletedCount).toBe(2);
+ const remaining = getEvents();
+ expect(remaining).toHaveLength(1);
+ expect(remaining[0].id).toBe('3');
+ });
+});
+```
+
+### 통합 테스트
+```typescript
+test('반복 일정 삭제 플로우', async () => {
+ // 1. 반복 일정 생성
+ // 2. 삭제 버튼 클릭
+ // 3. 확인 다이얼로그 표시
+ // 4. "예" 클릭 → 단일 삭제
+ // 5. 해당 일정만 삭제 확인
+
+ // 6. 다른 반복 일정 삭제 버튼 클릭
+ // 7. "아니오" 클릭 → 전체 삭제
+ // 8. 모든 반복 일정 삭제 확인
+ // 9. 피드백 메시지 확인
+});
+```
+
+### 수동 테스트 체크리스트
+- [ ] 일반 일정 삭제 시 다이얼로그 표시 안 함
+- [ ] 반복 일정 삭제 시 다이얼로그 표시
+- [ ] "예" 선택 시 해당 일정만 삭제
+- [ ] "아니오" 선택 시 전체 삭제
+- [ ] "취소" 선택 시 삭제 안 함
+- [ ] 삭제 후 적절한 피드백 메시지 표시
+- [ ] 전체 삭제 시 삭제된 개수 표시
+- [ ] 캘린더와 목록에서 삭제된 일정 사라짐 확인
+
+## 🔗 관련 기능
+- [반복 일정 생성](./01-repeat-event-creation.md)
+- [반복 일정 수정](./03-repeat-event-update.md)
+- [반복 일정 생성 로직](./05-repeat-event-generation.md)
+
diff --git a/.cursor/spec/stories/04-repeat-event-update.md b/.cursor/spec/stories/04-repeat-event-update.md
new file mode 100644
index 00000000..d4a5bcb6
--- /dev/null
+++ b/.cursor/spec/stories/04-repeat-event-update.md
@@ -0,0 +1,56 @@
+---
+title: 반복 일정 수정 스토리
+category: calendar
+type: story
+updated: 2025-10-30
+---
+
+## 목적
+반복 일정을 수정할 때 사용자에게 단일 수정과 전체 수정을 선택하게 하고, 선택에 따라 적절히 저장한다.
+
+## 사용자 시나리오
+1. 사용자가 반복 일정(반복 아이콘 표시)을 편집한다.
+2. 저장 시 확인 다이얼로그가 뜬다: “해당 일정만 수정하시겠어요?”
+3. 사용자가 선택한다:
+ - 예(단일): 해당 인스턴스만 수정되고 반복에서 분리된다(`repeat.type = 'none'`).
+ - 아니오(전체): 동일 반복 그룹의 모든 인스턴스가 수정된다(`repeat.id`로 식별).
+
+## 동작 규칙
+- 단일 수정 시
+ - 현재 편집 중 이벤트만 `PUT /api/events/:id`
+ - 본문에서 `repeat.type = 'none'`로 지정하여 반복에서 분리
+ - UI에서 해당 이벤트의 반복 아이콘 제거
+- 전체 수정 시
+ - `PUT /api/recurring-events/:repeatId`로 일괄 수정
+ - UI에서 반복 아이콘 유지
+
+## 데이터/타입
+- `RepeatInfo`: `{ type: 'none'|'daily'|'weekly'|'monthly'|'yearly', interval: number, endDate?: string, id?: string }`
+- 반복 그룹 식별: `repeat.id`(repeatGroupId)
+
+## API
+- 단일 수정: `PUT /api/events/:id` (본문: `Event`)
+- 전체 수정: `PUT /api/recurring-events/:repeatId` (본문: `EventForm` 또는 변경 필드)
+- 테스트에서는 `msw`로 해당 엔드포인트를 `server.use`로 오버라이드해 검증
+
+## UI
+- 저장 버튼 클릭 시 확인 다이얼로그 노출
+- 예/아니오 버튼에 `aria-label` 제공: `aria-label="단일 수정"`, `aria-label="전체 수정"`
+- 결과에 따라 반복 아이콘 표시 여부 변경(단일: 제거, 전체: 유지)
+
+## 접근성
+- 다이얼로그에 역할/라벨 제공
+- 버튼에 명확한 `aria-label`
+
+## 테스트 케이스 (TDD)
+1) 훅 테스트(실패 → 구현)
+ - 편집 모드에서 단일 선택 시 `PUT /api/events/:id` 호출, 본문에 `repeat.type='none'`
+ - 편집 모드에서 전체 선택 시 `PUT /api/recurring-events/:repeatId` 호출
+2) UI 테스트(실패 → 구현)
+ - 저장 시 다이얼로그 노출 및 두 버튼 존재
+ - 단일 선택 시 리스트의 해당 항목에서 반복 아이콘 제거
+ - 전체 선택 시 반복 아이콘 유지
+
+## Out of Scope
+- 반복 생성/삭제는 별도 스토리에서 다룸(01/05)
+
diff --git a/.cursor/spec/stories/05-repeat-event-delete.md b/.cursor/spec/stories/05-repeat-event-delete.md
new file mode 100644
index 00000000..7908e31f
--- /dev/null
+++ b/.cursor/spec/stories/05-repeat-event-delete.md
@@ -0,0 +1,36 @@
+---
+title: 반복 일정 삭제 스토리
+category: calendar
+type: story
+updated: 2025-10-30
+---
+
+## 목적
+반복 일정을 삭제할 때 사용자에게 단일 삭제와 전체 삭제를 선택하게 하고, 선택에 따라 적절히 삭제한다.
+
+## 사용자 시나리오
+1. 사용자가 반복 일정(반복 아이콘 표시)을 삭제하려고 한다.
+2. 삭제 시 확인 다이얼로그가 뜬다: “해당 일정만 삭제하시겠어요?”
+3. 사용자가 선택한다:
+ - 예(단일): 해당 인스턴스만 삭제된다.
+ - 아니오(전체): 동일 반복 그룹의 모든 인스턴스가 삭제된다(`repeat.id`).
+
+## 동작 규칙
+- 단일 삭제: `DELETE /api/events/:id`
+- 전체 삭제: `DELETE /api/recurring-events/:repeatId`
+- UI: 다이얼로그 버튼 `aria-label="단일 삭제"`, `aria-label="전체 삭제"`
+
+## 접근성
+- 다이얼로그에 제목/본문 제공, 버튼 라벨 명확화
+
+## 테스트 케이스 (TDD)
+1) 훅 테스트(실패 → 구현)
+ - 단일 삭제 선택 시 `/api/events/:id` 호출, 전체 삭제 호출 없음
+ - 전체 삭제 선택 시 `/api/recurring-events/:repeatId` 호출, 단일 삭제 호출 없음
+2) UI 테스트(실패 → 구현)
+ - 삭제 버튼 클릭 시 확인 다이얼로그 노출
+ - 단일 삭제 선택 시 리스트에서 해당 일정 제거, 전체 삭제 선택 시 동일 그룹 모두 제거(간단히 네트워크 카운트로 검증)
+
+## Out of Scope
+- 영구 삭제 취소/undo, 휴지통 등 추가 UX는 범위 외
+
diff --git a/.cursor/spec/stories/05-repeat-event-generation.md b/.cursor/spec/stories/05-repeat-event-generation.md
new file mode 100644
index 00000000..eed2e17e
--- /dev/null
+++ b/.cursor/spec/stories/05-repeat-event-generation.md
@@ -0,0 +1,335 @@
+# 반복 일정 생성 로직 설계
+
+## 📋 개요
+반복 설정에 따라 실제 이벤트 인스턴스를 생성하는 핵심 로직을 설계합니다.
+
+## 🎯 목적
+- 반복 설정(매일/매주/매월/매년)에 따라 이벤트 생성
+- 특수 케이스 처리 (31일, 윤년 2월 29일)
+- 2025-12-31까지 생성 제한
+- 반복 일정 겹침은 고려하지 않음
+
+## 📥 입력 (Input)
+
+```typescript
+interface RepeatEventGenerationInput {
+ baseEvent: EventForm; // 기본 이벤트 데이터
+ repeat: {
+ type: RepeatType; // 'daily' | 'weekly' | 'monthly' | 'yearly'
+ interval: number; // 반복 간격
+ endDate?: string; // 종료일 (최대 2025-12-31)
+ };
+}
+```
+
+## ⚙️ 처리 로직 (Process)
+
+### 1. 유틸리티 함수 생성 (`src/utils/repeatUtils.ts`)
+
+```typescript
+import { EventForm, RepeatType } from '../types';
+
+const MAX_END_DATE = new Date('2025-12-31');
+
+/**
+ * 반복 일정 생성
+ */
+export function generateRepeatEvents(
+ baseEvent: EventForm
+): EventForm[] {
+ if (baseEvent.repeat.type === 'none') {
+ return [];
+ }
+
+ const events: EventForm[] = [];
+ const startDate = new Date(baseEvent.date);
+ const endDate = baseEvent.repeat.endDate
+ ? new Date(baseEvent.repeat.endDate)
+ : MAX_END_DATE;
+
+ let currentDate = new Date(startDate);
+ let count = 0;
+ const MAX_ITERATIONS = 1000; // 무한 루프 방지
+
+ while (currentDate <= endDate && count < MAX_ITERATIONS) {
+ const eventDate = formatDate(currentDate);
+
+ events.push({
+ ...baseEvent,
+ date: eventDate,
+ });
+
+ currentDate = getNextRepeatDate(
+ currentDate,
+ baseEvent.repeat.type,
+ baseEvent.repeat.interval,
+ startDate
+ );
+
+ count++;
+ }
+
+ return events;
+}
+
+/**
+ * 다음 반복 날짜 계산
+ */
+function getNextRepeatDate(
+ current: Date,
+ type: RepeatType,
+ interval: number,
+ originalDate: Date
+): Date {
+ const next = new Date(current);
+
+ switch (type) {
+ case 'daily':
+ next.setDate(next.getDate() + interval);
+ break;
+
+ case 'weekly':
+ next.setDate(next.getDate() + (7 * interval));
+ break;
+
+ case 'monthly':
+ return getNextMonthlyDate(current, interval, originalDate);
+
+ case 'yearly':
+ return getNextYearlyDate(current, interval, originalDate);
+
+ default:
+ break;
+ }
+
+ return next;
+}
+
+/**
+ * 매월 반복 날짜 계산
+ * 31일 규칙: 31일이 있는 달에만 생성
+ */
+function getNextMonthlyDate(
+ current: Date,
+ interval: number,
+ original: Date
+): Date {
+ const originalDay = original.getDate();
+ let next = new Date(current);
+ let attempts = 0;
+ const MAX_ATTEMPTS = 24; // 최대 2년
+
+ while (attempts < MAX_ATTEMPTS) {
+ next.setMonth(next.getMonth() + interval);
+
+ // 해당 월의 마지막 날 확인
+ const daysInMonth = new Date(
+ next.getFullYear(),
+ next.getMonth() + 1,
+ 0
+ ).getDate();
+
+ // 원본 날짜가 해당 월에 존재하는 경우
+ if (originalDay <= daysInMonth) {
+ next.setDate(originalDay);
+ break;
+ }
+
+ attempts++;
+ }
+
+ return next;
+}
+
+/**
+ * 매년 반복 날짜 계산
+ * 윤년 2/29 규칙: 윤년에만 생성
+ */
+function getNextYearlyDate(
+ current: Date,
+ interval: number,
+ original: Date
+): Date {
+ const originalMonth = original.getMonth();
+ const originalDay = original.getDate();
+ let next = new Date(current);
+ let attempts = 0;
+ const MAX_ATTEMPTS = 10;
+
+ // 윤년 2월 29일 체크
+ const isLeapDayOriginal = originalMonth === 1 && originalDay === 29;
+
+ while (attempts < MAX_ATTEMPTS) {
+ next.setFullYear(next.getFullYear() + interval);
+
+ if (isLeapDayOriginal) {
+ // 윤년인지 확인
+ if (isLeapYear(next.getFullYear())) {
+ next.setMonth(1);
+ next.setDate(29);
+ break;
+ }
+ } else {
+ next.setMonth(originalMonth);
+ next.setDate(originalDay);
+ break;
+ }
+
+ attempts++;
+ }
+
+ return next;
+}
+
+/**
+ * 윤년 판별
+ */
+function isLeapYear(year: number): boolean {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
+
+/**
+ * 날짜 포맷 (YYYY-MM-DD)
+ */
+function formatDate(date: Date): string {
+ return date.toISOString().split('T')[0];
+}
+```
+
+### 2. API 통합 (클라이언트 → 서버)
+
+```typescript
+// 저장 시 클라이언트에서 인스턴스 생성 후 일괄 저장
+const instances = generateRepeatEvents(baseEvent);
+await fetch('/api/events-list', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ events: instances }),
+});
+
+// 서버는 각 이벤트에 id를, 반복 시리즈에는 공통 repeat.id를 부여하여 응답
+```
+
+## 📤 출력 (Output)
+
+### 예시: 매주 반복
+```typescript
+// Input
+{
+ title: "주간 회의",
+ date: "2025-01-06",
+ repeat: { type: "weekly", interval: 1, endDate: "2025-01-31" }
+}
+
+// Output: 4개 생성(서버 저장 후 각 이벤트에 id와 repeat.id 부여)
+[
+ { id: "...", date: "2025-01-06", repeat: { type: 'weekly', interval: 1, id: "series-1" }, ... },
+ { id: "...", date: "2025-01-13", repeat: { type: 'weekly', interval: 1, id: "series-1" }, ... },
+ { id: "...", date: "2025-01-20", repeat: { type: 'weekly', interval: 1, id: "series-1" }, ... },
+ { id: "...", date: "2025-01-27", repeat: { type: 'weekly', interval: 1, id: "series-1" }, ... },
+]
+```
+
+### 예시: 매월 31일 (특수 케이스)
+```typescript
+// Input: 2025-01-31부터 매월
+// Output: 31일이 있는 달만
+[
+ { date: "2025-01-31" },
+ { date: "2025-03-31" },
+ { date: "2025-05-31" },
+ { date: "2025-07-31" },
+ { date: "2025-08-31" },
+ { date: "2025-10-31" },
+ { date: "2025-12-31" },
+]
+// 2월, 4월, 6월, 9월, 11월은 생략
+```
+
+### 예시: 윤년 2월 29일 (특수 케이스)
+```typescript
+// Input: 2024-02-29부터 매년
+// Output: 윤년만
+[
+ { date: "2024-02-29" },
+ // 2025년 생략 (평년)
+]
+```
+
+## 📁 영향받는 파일
+
+### 새로 생성
+1. ✨ `src/utils/repeatUtils.ts` - 반복 로직 유틸리티
+2. ✨ `src/__tests__/unit/easy.repeatUtils.spec.ts` - 단위 테스트
+
+### 수정 필요
+3. 🔧 `server.js` - 반복 일정 생성 통합
+4. 🔧 `src/hooks/useEventOperations.ts` - 클라이언트 생성 로직 (선택)
+
+## 🚨 고려사항
+
+### 생성 위치
+**서버 측 생성 (권장)**
+- 장점: 일관성, 클라이언트 부담 감소
+- 단점: 서버 부하
+
+**클라이언트 측 생성 (대안)**
+- 장점: 서버 부하 감소
+- 단점: 일관성 유지 어려움
+
+### 성능
+- 최대 생성 개수: 365개 (매일, 1년)
+- 무한 루프 방지: MAX_ITERATIONS 제한
+- 효율적인 날짜 계산 알고리즘
+
+### 반복 일정 조회 최적화
+```typescript
+// 뷰 렌더링 시 필터링
+const visibleEvents = events.filter(event => {
+ const eventDate = new Date(event.date);
+ return eventDate >= viewStartDate && eventDate <= viewEndDate;
+});
+```
+
+## ✅ 검증 방법
+
+### 단위 테스트
+```typescript
+describe('반복 일정 생성', () => {
+ test('매일 반복', () => {
+ const events = generateRepeatEvents({
+ date: '2025-01-01',
+ repeat: { type: 'daily', interval: 1, endDate: '2025-01-07' },
+ // ...
+ });
+
+ expect(events).toHaveLength(7);
+ });
+
+ test('매월 31일 - 31일 있는 달만', () => {
+ const events = generateRepeatEvents({
+ date: '2025-01-31',
+ repeat: { type: 'monthly', interval: 1, endDate: '2025-12-31' },
+ // ...
+ });
+
+ expect(events).toHaveLength(7); // 1,3,5,7,8,10,12월
+ expect(events.find(e => e.date === '2025-02-28')).toBeUndefined();
+ });
+
+ test('윤년 2월 29일 - 윤년만', () => {
+ const events = generateRepeatEvents({
+ date: '2024-02-29',
+ repeat: { type: 'yearly', interval: 1, endDate: '2025-12-31' },
+ // ...
+ });
+
+ expect(events).toHaveLength(1); // 2024년만
+ });
+});
+```
+
+## 🔗 관련 기능
+- [반복 일정 생성](./01-repeat-event-creation.md)
+- [반복 일정 표시](./02-repeat-event-display.md)
+
diff --git a/.cursor/spec/stories/README.md b/.cursor/spec/stories/README.md
new file mode 100644
index 00000000..5e7888b0
--- /dev/null
+++ b/.cursor/spec/stories/README.md
@@ -0,0 +1,238 @@
+# 반복 일정 기능 설계 문서
+
+## 📚 문서 개요
+이 디렉토리는 캘린더 애플리케이션의 **반복 일정 기능**에 대한 상세 설계 문서를 포함합니다.
+
+## 🎯 기능 목표
+사용자가 일정을 생성할 때 반복 설정(매일/매주/매월/매년)을 선택하고, 이를 캘린더에 표시하며, 개별 또는 전체 수정/삭제가 가능하도록 합니다.
+
+## 📋 설계 문서 목록
+
+### 1. [반복 일정 생성 및 선택](./01-repeat-event-creation.md)
+**목적**: 일정 폼에서 반복 유형 선택 UI 및 데이터 저장
+
+**주요 내용**:
+- 반복 유형 선택 (매일/매주/매월/매년)
+- 반복 간격 설정
+- 반복 종료일 설정 (최대 2025-12-31)
+- 유효성 검증
+
+**영향받는 파일**:
+- `src/App.tsx` - UI 활성화
+- `src/hooks/useEventForm.ts` - 상태 관리
+- `src/utils/repeatValidation.ts` - 검증 로직 (신규)
+
+---
+
+### 2. [반복 일정 표시](./02-repeat-event-display.md)
+**목적**: 캘린더와 목록에서 반복 일정을 시각적으로 구분
+
+**주요 내용**:
+- 반복 아이콘 표시 (Material-UI Repeat 아이콘)
+- 월간/주간 뷰에서 아이콘 표시
+- 일정 목록에서 반복 정보 표시
+- 접근성 고려 (aria-label)
+
+**영향받는 파일**:
+- `src/App.tsx` - 렌더링 로직 수정
+- `src/utils/eventUtils.ts` - 유틸리티 함수 추가
+
+---
+
+### 3. [반복 일정 수정](./03-repeat-event-update.md)
+**목적**: 반복 일정 수정 시 단일/전체 선택 기능
+
+**주요 내용**:
+- 수정 확인 다이얼로그 ("해당 일정만 수정하시겠어요?")
+- 단일 수정: 반복에서 분리, 아이콘 제거
+- 전체 수정: 모든 반복 일정 업데이트
+- repeat.id로 그룹 관리
+
+**영향받는 파일**:
+- `src/types.ts` - `RepeatInfo.id?` 필드 추가
+- `src/App.tsx` - 확인 다이얼로그
+- `src/hooks/useEventOperations.ts` - 전체 수정 로직
+- `server.js` - API 엔드포인트 추가
+
+---
+
+### 4. [반복 일정 삭제](./04-repeat-event-delete.md)
+**목적**: 반복 일정 삭제 시 단일/전체 선택 기능
+
+**주요 내용**:
+- 삭제 확인 다이얼로그 ("해당 일정만 삭제하시겠어요?")
+- 단일 삭제: 해당 일정만 제거
+- 전체 삭제: 모든 반복 일정 제거
+- 안전성 고려 (실수 방지)
+
+**영향받는 파일**:
+- `src/App.tsx` - 확인 다이얼로그
+- `src/hooks/useEventOperations.ts` - 전체 삭제 로직
+- `server.js` - API 엔드포인트 추가
+
+---
+
+### 5. [반복 일정 생성 로직](./05-repeat-event-generation.md)
+**목적**: 반복 설정에 따라 실제 이벤트 인스턴스 생성
+
+**주요 내용**:
+- 매일/매주/매월/매년 반복 계산
+- **특수 케이스**: 매월 31일 → 31일 있는 달만
+- **특수 케이스**: 윤년 2/29 → 윤년만
+- 2025-12-31까지 제한
+- 반복 일정 겹침 무시
+
+**영향받는 파일**:
+- `src/utils/repeatUtils.ts` - 반복 생성 로직 (신규)
+- `server.js` - API 통합
+
+---
+
+## 🏗️ 아키텍처 개요
+
+```
+┌─────────────────────────────────────────────────────┐
+│ 사용자 입력 │
+│ (반복 유형, 간격, 종료일) │
+└───────────────────┬─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────┐
+│ useEventForm 훅 │
+│ (상태 관리 + 유효성 검증) │
+└───────────────────┬─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────┐
+│ useEventOperations 훅 │
+│ (API 호출 + 이벤트 CRUD) │
+└───────────────────┬─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────┐
+│ Server API │
+│ (반복 일정 생성 + 그룹 관리) │
+└───────────────────┬─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────┐
+│ 반복 일정 생성 로직 │
+│ (repeatUtils.ts - 날짜 계산) │
+└───────────────────┬─────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────┐
+│ 캘린더 표시 │
+│ (App.tsx - 반복 아이콘 표시) │
+└─────────────────────────────────────────────────────┘
+```
+
+## 🔑 핵심 개념
+
+### repeat.id (시리즈 식별자)
+- 같은 반복 그룹의 일정들을 식별하는 고유 ID
+- 전체 수정/삭제 시 사용
+- 형식: `"repeat-{timestamp}-{random}"`
+
+### 단일 vs 전체 수정/삭제
+| 작업 | 단일 | 전체 |
+|------|------|------|
+| **수정** | 반복에서 분리 (`repeat.type = 'none'`) | 그룹 전체 업데이트 |
+| **삭제** | 해당 일정만 제거 | 그룹 전체 제거 |
+| **아이콘** | 사라짐 | 유지 |
+| **API** | `PUT /api/events/:id` | `PUT /api/recurring-events/:repeatId` |
+
+### 특수 케이스 처리
+1. **매월 31일**: 31일이 있는 달에만 생성 (1,3,5,7,8,10,12월)
+2. **윤년 2월 29일**: 윤년에만 생성
+3. **반복 종료일**: 최대 2025-12-31까지
+4. **겹침**: 반복 일정은 겹침 체크 안 함
+
+## 📁 파일 구조
+
+```
+src/
+├── types.ts # RepeatInfo.id 추가
+├── App.tsx # UI 수정 (아이콘, 다이얼로그)
+├── hooks/
+│ ├── useEventForm.ts # 반복 상태 관리
+│ └── useEventOperations.ts # 그룹 수정/삭제 추가
+├── utils/
+│ ├── repeatValidation.ts # 유효성 검증 (신규)
+│ ├── repeatUtils.ts # 반복 생성 로직 (신규)
+│ └── eventUtils.ts # 반복 관련 유틸 추가
+└── __tests__/
+ ├── unit/
+ │ ├── easy.repeatValidation.spec.ts (신규)
+ │ ├── easy.repeatUtils.spec.ts (신규)
+ │ └── easy.eventUtils.spec.ts (수정)
+ └── integration/
+ ├── repeat-update.spec.tsx (신규)
+ └── repeat-delete.spec.tsx (신규)
+
+server.js # 반복 그룹 API 추가
+```
+
+## 🔄 작업 순서 (권장)
+
+### Phase 1: 기본 구조
+1. ✅ 타입 정의 확장 (`RepeatInfo.id` 추가)
+2. ✅ 반복 생성 로직 구현 (`repeatUtils.ts`)
+3. ✅ 유효성 검증 구현 (`repeatValidation.ts`)
+4. ✅ 단위 테스트 작성
+
+### Phase 2: UI 구현
+5. ✅ 반복 설정 UI 활성화 (App.tsx)
+6. ✅ 반복 아이콘 표시 (eventUtils.ts, App.tsx)
+7. ✅ 확인 다이얼로그 추가 (수정/삭제)
+
+### Phase 3: API 통합
+8. ✅ 서버 API 구현 (반복 생성, 그룹 수정, 그룹 삭제)
+9. ✅ 훅 업데이트 (useEventOperations)
+10. ✅ 통합 테스트
+
+### Phase 4: 테스트 및 최적화
+11. ✅ 특수 케이스 테스트 (31일, 윤년)
+12. ✅ 성능 최적화
+13. ✅ 접근성 검증
+
+## ⚠️ 주의사항
+
+### 필수 구현 사항
+- ✅ 매월 31일 → 31일 있는 달만 (마지막 날 X)
+- ✅ 윤년 2월 29일 → 윤년만
+- ✅ 2025-12-31까지 제한
+- ✅ 반복 일정 겹침 무시
+
+### 사용자 경험
+- 확인 다이얼로그 문구 명확히 ("해당 일정만 ~")
+- 삭제된 개수 피드백 ("N개 삭제됨")
+- 로딩 상태 표시 (대량 작업 시)
+- 에러 처리 및 사용자 피드백
+
+### 성능 고려
+- 최대 365개 일정 생성 (매일, 1년)
+- 무한 루프 방지 (MAX_ITERATIONS)
+- 서버 측 배치 처리
+- 클라이언트 캐시 최적화
+
+## 📝 테스트 체크리스트
+
+### 단위 테스트
+- [ ] 반복 날짜 계산 (매일/매주/매월/매년)
+- [ ] 매월 31일 특수 케이스
+- [ ] 윤년 2월 29일 특수 케이스
+- [ ] 유효성 검증 (종료일, 간격)
+- [ ] 반복 아이콘 표시 로직
+
+### 통합 테스트
+- [ ] 반복 일정 생성 플로우
+- [ ] 단일/전체 수정 플로우
+- [ ] 단일/전체 삭제 플로우
+- [ ] 캘린더 표시 (아이콘 포함)
+
+
+## ▶️ 실행 방법
+- 개발 서버 실행: `pnpm start`
+- 접속 URL: `http://localhost:5173`
+
diff --git a/.cursor/templates/ceo-approval.tmpl.md b/.cursor/templates/ceo-approval.tmpl.md
new file mode 100644
index 00000000..5be0fe05
--- /dev/null
+++ b/.cursor/templates/ceo-approval.tmpl.md
@@ -0,0 +1,17 @@
+# ✅ CEO 승인 보고서
+
+## 메타데이터
+- 역할: CEO 승인
+- 날짜: {{DATE}}
+- Feature: {{FEATURE}}
+
+---
+
+## 승인 내용
+- 결론: 승인(✅) / 보완(⚠️)
+- 코멘트: {{SUMMARY}}
+
+---
+
+## 커밋 로그
+- (단계 통과 시 아래에 한 줄씩 추가)
diff --git a/.cursor/templates/code-implementation-review.tmpl.md b/.cursor/templates/code-implementation-review.tmpl.md
new file mode 100644
index 00000000..24b59c0b
--- /dev/null
+++ b/.cursor/templates/code-implementation-review.tmpl.md
@@ -0,0 +1,21 @@
+# 👨💻 구현 결과 보고서
+
+## 메타데이터
+- 에이전트: 구현
+- 날짜: {{DATE}}
+- Feature: {{FEATURE}}
+
+---
+
+## 변경 요약
+- 요점: {{SUMMARY}}
+
+---
+
+## 실행 로그
+- lint/test/build 상태: {{RUN_STATUS}}
+
+---
+
+## 커밋 로그
+- (단계 통과 시 아래에 한 줄씩 추가)
diff --git a/.cursor/templates/feature-verification-review.tmpl.md b/.cursor/templates/feature-verification-review.tmpl.md
new file mode 100644
index 00000000..fce55b4b
--- /dev/null
+++ b/.cursor/templates/feature-verification-review.tmpl.md
@@ -0,0 +1,21 @@
+# ✅ 기능 검증 결과 보고서
+
+## 메타데이터
+- 에이전트: 기능 검증
+- 날짜: {{DATE}}
+- Feature: {{FEATURE}}
+
+---
+
+## 검증 요약
+- 시나리오/결과: {{SUMMARY}}
+
+---
+
+## 실행 로그
+- lint/test/build 상태: {{RUN_STATUS}}
+
+---
+
+## 커밋 로그
+- (단계 통과 시 아래에 한 줄씩 추가)
diff --git a/.cursor/templates/test-code-review.tmpl.md b/.cursor/templates/test-code-review.tmpl.md
new file mode 100644
index 00000000..5e71135d
--- /dev/null
+++ b/.cursor/templates/test-code-review.tmpl.md
@@ -0,0 +1,23 @@
+# ✅ 테스트코드 결과 보고서
+
+## 메타데이터
+- 에이전트: 테스트코드
+- 날짜: {{DATE}}
+- Feature: {{FEATURE}}
+- 사용 체크리스트: `.cursor/checklist/test-code-agent-checklist.md`
+
+---
+
+## 변경 요약
+- 커버리지/주요 케이스: {{SUMMARY}}
+
+---
+
+## 실행 로그
+- lint/test/build 상태: {{RUN_STATUS}}
+
+---
+
+## 커밋 로그
+- (단계 통과 시 아래에 한 줄씩 추가)
+
diff --git a/.cursor/templates/test-design-review.tmpl.md b/.cursor/templates/test-design-review.tmpl.md
new file mode 100644
index 00000000..d883b196
--- /dev/null
+++ b/.cursor/templates/test-design-review.tmpl.md
@@ -0,0 +1,26 @@
+# 🧪 테스트설계 결과 보고서
+
+## 메타데이터
+- 에이전트: 테스트설계
+- 날짜: {{DATE}}
+- 승인자: CEO(Riku)
+- 관련 커밋/PR:
+- 참고 문서: `.cursor/docs/tdd-document.md`, `.cursorrules`
+- 사용 체크리스트: `.cursor/checklist/test-design-agent-checklist.md`
+
+---
+
+## 요약
+- Feature: {{FEATURE}}
+- 통과(✅)/주의(⚠️): {{PASS}}/{{WARN}}
+
+---
+
+## 상세 체크 결과
+- 근거 파일: {{EVIDENCE}}
+
+---
+
+## 커밋 로그
+- (단계 통과 시 아래에 한 줄씩 추가)
+
diff --git a/.cursorrules b/.cursorrules
new file mode 100644
index 00000000..5cc29930
--- /dev/null
+++ b/.cursorrules
@@ -0,0 +1,655 @@
+# 📅 캘린더 애플리케이션 개발 규칙
+
+## 🎯 프로젝트 개요
+
+React TypeScript 기반의 **캘린더 일정 관리 시스템**입니다.
+
+### 핵심 아키텍처 원칙
+1. **관심사 분리**: Hooks(로직) / Utils(순수 함수) / Components(UI)
+2. **테스트 주도 개발**: 한글 설명의 포괄적인 테스트 커버리지
+3. **타입 안전성**: 엄격한 TypeScript, 명시적 타입 정의
+4. **접근성 우선**: ARIA 레이블 및 시맨틱 HTML
+5. **성능 최적화**: React 최적화 패턴 적절히 사용
+
+### 주요 명령어
+```bash
+pnpm dev # 개발 서버 시작 (서버 + 클라이언트)
+pnpm test # 테스트 실행
+pnpm lint # 코드 품질 검사
+pnpm build # 프로덕션 빌드
+```
+
+---
+
+## 📂 프로젝트 구조
+
+### 필수 디렉토리 구조
+```
+src/
+├── App.tsx # 메인 애플리케이션 컴포넌트
+├── types.ts # TypeScript 타입 정의
+├── main.tsx # 진입점
+├── hooks/ # 커스텀 React 훅만 위치
+│ ├── useEventForm.ts
+│ ├── useCalendarView.ts
+│ ├── useEventOperations.ts
+│ ├── useNotifications.ts
+│ └── useSearch.ts
+├── utils/ # 순수 함수만 위치
+│ ├── dateUtils.ts
+│ ├── eventOverlap.ts
+│ ├── eventUtils.ts
+│ ├── notificationUtils.ts
+│ └── timeValidation.ts
+├── apis/ # API 관련 함수만 위치
+│ └── fetchHolidays.ts
+├── __tests__/ # 모든 테스트 파일
+│ ├── hooks/ # 훅 테스트
+│ ├── unit/ # 단위 테스트
+│ └── medium.integration.spec.tsx
+└── __mocks__/ # 목 데이터 및 핸들러
+```
+
+### 절대 금지 사항
+- ❌ 비즈니스 로직을 컴포넌트에 직접 작성
+- ❌ 유틸리티 함수와 React 훅 혼합
+- ❌ 부모 디렉토리에서 두 번 이상 import (`../../` 금지)
+- ❌ 정해진 디렉토리 구조 외부에 파일 생성
+
+---
+
+## 🔒 TypeScript 규칙
+
+### 필수 사항
+- **반드시** 모든 데이터 구조에 인터페이스 정의
+- **반드시** 제한된 값 집합에는 유니온 타입 사용
+- **반드시** 복잡한 함수에 JSDoc 주석 추가
+- **반드시** 함수의 명시적 반환 타입 지정
+- **반드시** 함수 파라미터 타입 지정
+- **반드시** 불변 배열에 `as const` 사용
+
+### 절대 금지
+- ❌ `any` 타입 사용 (필요시 `unknown` 사용)
+- ❌ TypeScript 에러 무시
+- ❌ 설명 없이 `@ts-ignore` 사용
+- ❌ 과도하게 복잡한 제네릭 타입 생성
+
+### 올바른 타입 정의 예시
+```typescript
+/**
+ * 캘린더 이벤트를 나타내는 인터페이스
+ */
+export interface Event {
+ id: string;
+ title: string;
+ date: string; // YYYY-MM-DD 형식
+ startTime: string; // HH:MM 형식
+ endTime: string; // HH:MM 형식
+ description: string;
+ location: string;
+ category: string;
+ repeat: RepeatInfo;
+ notificationTime: number;
+}
+
+export type RepeatType = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly';
+
+type TimeErrorRecord = Record<'startTimeError' | 'endTimeError', string | null>;
+```
+
+---
+
+## ⚛️ React & Hooks 규칙
+
+### 커스텀 훅 구조 (필수 패턴)
+```typescript
+export const useCustomHook = (initialValue?: Type) => {
+ // 1. 상태 선언
+ const [state, setState] = useState(initialValue || defaultValue);
+
+ // 2. 파생 상태
+ const [errorState, setErrorState] = useState({});
+
+ // 3. 이벤트 핸들러
+ const handleEvent = (e: ChangeEvent) => {
+ // 핸들러 로직
+ };
+
+ // 4. 유틸리티 함수
+ const resetFunction = () => {
+ // 리셋 로직
+ };
+
+ // 5. 객체로 반환 (배열 금지)
+ return {
+ state,
+ setState,
+ errorState,
+ handleEvent,
+ resetFunction,
+ };
+};
+```
+
+### 훅 규칙 (절대 위반 금지)
+- **반드시** 커스텀 훅은 객체를 반환 (배열 반환 금지)
+- **반드시** 커스텀 훅 이름은 `use`로 시작
+- **반드시** 로딩 및 에러 상태 처리
+- **절대** 조건부로 훅 호출 금지
+- **절대** 상태 직접 변경 금지
+
+### 상태 업데이트 패턴
+```typescript
+// ✅ 올바른 방법
+const updateState = (newValue: string) => {
+ setState(prev => ({ ...prev, property: newValue }));
+};
+
+// ❌ 잘못된 방법
+const updateState = (newValue: string) => {
+ state.property = newValue; // 직접 변경 금지!
+};
+```
+
+### 컴포넌트 구조 패턴
+```typescript
+function ComponentName() {
+ // 1. 커스텀 훅 먼저
+ const { data, setData } = useCustomHook();
+
+ // 2. 로컬 상태
+ const [localState, setLocalState] = useState();
+
+ // 3. 이벤트 핸들러
+ const handleEvent = useCallback(() => {
+ // 로직
+ }, [dependencies]);
+
+ // 4. 렌더 함수
+ const renderSection = () => {
+ return
{/* JSX */}
;
+ };
+
+ // 5. 메인 JSX 반환
+ return
{/* 컴포넌트 */}
;
+}
+```
+
+### Import 순서 (절대 준수)
+```typescript
+// 1. 외부 라이브러리 (Material-UI, React 등)
+import { Box, Button } from '@mui/material';
+import { useState, useCallback } from 'react';
+
+// 2. 내부 모듈 (hooks, utils, types)
+import { useEventForm } from './hooks/useEventForm.ts';
+import { Event } from './types';
+```
+
+---
+
+## 🎨 Material-UI & 스타일링
+
+### 컴포넌트 사용 (필수)
+```typescript
+// ✅ 올바른 Material-UI 사용
+
+
+ 제목
+ setValue(e.target.value)}
+ />
+
+
+```
+
+### 접근성 규칙 (절대 생략 금지)
+- **반드시** 폼 입력에 `id` 속성 제공
+- **반드시** 아이콘 버튼에 `aria-label` 사용
+- **반드시** 시맨틱 HTML 요소 사용
+- **반드시** 적절한 폼 레이블 제공
+
+### 폼 컴포넌트 패턴
+```typescript
+
+ 필드 레이블
+ setValue(e.target.value)}
+ error={!!error}
+ helperText={error}
+ />
+
+```
+
+### 아이콘 버튼 패턴
+```typescript
+
+
+
+```
+
+### 절대 금지
+- ❌ `sx` prop 대신 인라인 스타일 사용
+- ❌ 접근성 속성 누락
+- ❌ 하드코딩된 색상 (테마 색상 사용)
+- ❌ Material-UI에 있는데 커스텀 컴포넌트 생성
+
+---
+
+## 📅 캘린더 도메인 규칙
+
+### 날짜 처리 (필수)
+- **반드시** 날짜는 ISO 형식 사용 (YYYY-MM-DD)
+- **반드시** 시간은 24시간 형식 사용 (HH:MM)
+- **반드시** 테스트에서 타임존은 UTC로 처리
+- **반드시** 날짜 범위 및 윤년 검증
+
+### 이벤트 검증 규칙
+- **반드시** 시작 시간이 종료 시간보다 앞인지 검증
+- **반드시** 이벤트 겹침 확인
+- **반드시** 필수 필드 검증 (title, date, startTime, endTime)
+- **절대** 유효하지 않은 날짜/시간 조합 허용 금지
+
+### 이벤트 겹침 감지
+```typescript
+const findOverlappingEvents = (newEvent: EventForm, existingEvents: Event[]): Event[] => {
+ return existingEvents.filter(event => {
+ // 같은 날짜인지 확인
+ if (event.date !== newEvent.date) return false;
+
+ // 시간 겹침 로직
+ const newStart = newEvent.startTime;
+ const newEnd = newEvent.endTime;
+ const existingStart = event.startTime;
+ const existingEnd = event.endTime;
+
+ return (newStart < existingEnd && newEnd > existingStart);
+ });
+};
+```
+
+### 주(Week) 계산 규칙
+- **반드시** 주는 일요일부터 시작 (한국 캘린더 표준)
+- **반드시** 월 경계를 올바르게 처리
+- **반드시** 2월의 윤년 고려
+- **반드시** ISO 주에는 목요일 기반 주 번호 사용
+
+### 절대 금지
+- ❌ 계산에 로컬 타임존 사용
+- ❌ 윤년 엣지 케이스 무시
+- ❌ 여러 날에 걸친 이벤트 허용
+- ❌ 겹침 검증 생략
+
+---
+
+## 🌐 API & 데이터 관리
+
+### API 함수 구조 (필수)
+```typescript
+export const fetchData = async (): Promise => {
+ try {
+ const response = await fetch('/api/endpoint');
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return await response.json();
+ } catch (error) {
+ console.error('Failed to fetch data:', error);
+ throw error;
+ }
+};
+```
+
+### 에러 처리 규칙
+- **반드시** HTTP 에러를 적절히 처리
+- **반드시** 에러 발생 시 사용자 피드백 제공
+- **반드시** 디버깅을 위한 에러 로깅
+- **절대** API 에러를 조용히 무시 금지
+
+### 훅 통합 패턴
+```typescript
+export const useDataOperations = () => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const saveData = async (newData: DataType) => {
+ setLoading(true);
+ setError(null);
+ try {
+ const saved = await apiCall(newData);
+ setData(prev => [...prev, saved]);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { data, loading, error, saveData };
+};
+```
+
+### 절대 금지
+- ❌ API 응답에 `any` 타입 사용
+- ❌ 로딩 상태 무시
+- ❌ 에러 처리 없이 API 호출
+- ❌ API 엔드포인트 하드코딩
+
+---
+
+## ✅ 코드 품질 & 모범 사례
+
+### 코드 조직 (필수)
+- **반드시** 의미 있는 변수 및 함수 이름 사용
+- **반드시** 한 가지 일만 잘하는 함수 작성
+- **반드시** 상속보다 조합 선호
+- **반드시** 불변 값에 `const` 사용
+
+### 성능 규칙
+- **반드시** 자식 컴포넌트에 전달하는 이벤트 핸들러에 `useCallback` 사용
+- **반드시** 비용이 큰 계산에 `useMemo` 사용
+- **반드시** 렌더 중 객체/함수 생성 피하기
+- **절대** 불필요한 리렌더 발생시키지 않기
+
+### 상수 패턴
+```typescript
+// ✅ 올바른 방법 - 명명된 상수
+const CATEGORIES = ['업무', '개인', '가족', '기타'] as const;
+const WEEK_DAYS = ['일', '월', '화', '수', '목', '금', '토'] as const;
+const NOTIFICATION_OPTIONS = [
+ { value: 1, label: '1분 전' },
+ { value: 10, label: '10분 전' },
+ { value: 60, label: '1시간 전' },
+ { value: 120, label: '2시간 전' },
+ { value: 1440, label: '1일 전' },
+] as const;
+
+// ❌ 잘못된 방법 - 매직 넘버
+if (value === 1) { /* 1이 무엇을 의미하는가? */ }
+```
+
+### 함수 문서화
+```typescript
+/**
+ * 주어진 년도와 월의 일수를 계산합니다
+ * @param year - 년도 (예: 2025)
+ * @param month - 월 (1-12)
+ * @returns 해당 월의 일수
+ */
+export function getDaysInMonth(year: number, month: number): number {
+ return new Date(year, month, 0).getDate();
+}
+```
+
+### 절대 금지 (코드 스멜)
+- ❌ 50줄 이상의 함수 작성
+- ❌ 매직 넘버 사용 (명명된 상수 사용)
+- ❌ 코드 중복 (유틸리티로 추출)
+- ❌ ESLint 경고 무시
+
+---
+
+## 🧪 테스팅 표준
+
+### 테스트 구조 (필수)
+```typescript
+describe('함수명', () => {
+ it('유효한 입력이 주어지면 예상된 결과를 반환해야 함', () => {
+ // Arrange (준비)
+ const input = 'test-input';
+
+ // Act (실행)
+ const result = functionToTest(input);
+
+ // Assert (검증)
+ expect(result).toBe('expected-output');
+ });
+
+ it('엣지 케이스를 올바르게 처리해야 함', () => {
+ // 엣지 케이스 테스트
+ });
+});
+```
+
+### 테스트 작성 규칙
+- **반드시** 비즈니스 로직 설명은 한글로 작성
+- **반드시** 엣지 케이스 테스트 (윤년, 월 경계 등)
+- **반드시** 시나리오를 설명하는 서술적인 테스트 이름
+- **반드시** AAA 패턴 (Arrange, Act, Assert) 따르기
+
+### 테스트 데이터 패턴
+```typescript
+const mockEvents: Event[] = [
+ {
+ id: '1',
+ title: '테스트 이벤트',
+ date: '2025-07-01',
+ startTime: '09:00',
+ endTime: '10:00',
+ description: '',
+ location: '',
+ category: '업무',
+ repeat: { type: 'none', interval: 1 },
+ notificationTime: 10,
+ },
+];
+```
+
+### 훅 테스트 패턴
+```typescript
+describe('useCustomHook', () => {
+ it('올바른 기본값으로 초기화되어야 함', () => {
+ const { result } = renderHook(() => useCustomHook());
+
+ expect(result.current.state).toBe('default-value');
+ });
+
+ it('액션 호출 시 상태가 업데이트되어야 함', () => {
+ const { result } = renderHook(() => useCustomHook());
+
+ act(() => {
+ result.current.updateState('new-value');
+ });
+
+ expect(result.current.state).toBe('new-value');
+ });
+});
+```
+
+### 절대 금지
+- ❌ 단언(assertion) 없는 테스트 작성
+- ❌ 구현 세부사항 테스트
+- ❌ 에러 조건 테스트 생략
+- ❌ 테스트 데이터에 `any` 타입 사용
+
+---
+
+## 🛠️ 개발 워크플로우
+
+### 개발 명령어 (필수 사용)
+```bash
+pnpm dev # 개발 서버 시작 (서버 + 클라이언트)
+pnpm test # 테스트 실행
+pnpm test:coverage # 테스트 커버리지 확인
+pnpm lint # 코드 품질 검사
+pnpm build # 프로덕션 빌드 검증
+```
+
+### Git 워크플로우 규칙
+- **반드시** 논리적이고 원자적인 단위로 커밋
+- **반드시** 서술적인 커밋 메시지 작성
+- **반드시** 커밋 전 테스트 실행
+- **절대** 깨진 코드 커밋 금지
+
+### 커밋 전 체크리스트
+```bash
+# 모든 커밋 전에 실행
+pnpm lint # 코드 품질 확인
+pnpm test # 모든 테스트 실행
+pnpm build # 빌드 작동 확인
+```
+
+### 코드 리뷰 체크리스트
+- [ ] 모든 테스트 통과
+- [ ] TypeScript 에러 없음
+- [ ] ESLint 경고 해결
+- [ ] 접근성 속성 존재
+- [ ] 에러 처리 구현
+- [ ] 성능 고려사항 처리
+
+### 절대 금지
+- ❌ 린팅 없이 커밋
+- ❌ 새 기능 추가 시 테스트 생략
+- ❌ 대용량 파일이나 빌드 결과물 커밋
+- ❌ TypeScript 에러 무시
+
+---
+
+## 📋 반복 일정 기능 특별 규칙
+
+### 반복 일정 생성
+- **반드시** 반복 종료일은 2025-12-31까지로 제한
+- **반드시** 매월 31일 선택 시 31일이 있는 달에만 생성 (마지막 날 X)
+- **반드시** 윤년 2월 29일 선택 시 윤년에만 생성
+- **반드시** `repeatGroupId`로 반복 그룹 관리
+
+### 반복 일정 수정/삭제
+- **반드시** 사용자에게 "단일" vs "전체" 선택 옵션 제공
+- **반드시** 단일 수정 시 반복에서 분리 (`repeat.type = 'none'`)
+- **반드시** 전체 수정 시 `repeatGroupId`로 그룹 전체 업데이트
+- **반드시** 삭제 시 안전 확인 다이얼로그 표시
+
+### 반복 일정 표시
+- **반드시** 반복 아이콘으로 시각적 구분
+- **반드시** Material-UI `Repeat` 아이콘 사용
+- **반드시** 아이콘에 `aria-label="반복 일정"` 추가
+- **반드시** 우선순위: 알림 아이콘 > 반복 아이콘
+
+---
+
+## 🚫 절대 금지 사항 요약
+
+### 타입 & 코드
+- ❌ `any` 타입 사용
+- ❌ TypeScript 에러 무시
+- ❌ 매직 넘버 사용
+- ❌ 50줄 이상 함수
+- ❌ 코드 중복
+
+### React & 훅
+- ❌ 훅을 조건부로 호출
+- ❌ 상태 직접 변경
+- ❌ useEffect에서 의존성 배열 생략
+- ❌ 커스텀 훅이 배열 반환
+- ❌ 컴포넌트에 비즈니스 로직
+
+### 스타일 & UI
+- ❌ 접근성 속성 누락
+- ❌ 인라인 스타일 사용
+- ❌ 하드코딩된 색상
+- ❌ 시맨틱 HTML 미사용
+
+### API & 데이터
+- ❌ 에러 처리 없이 API 호출
+- ❌ 로딩 상태 무시
+- ❌ API 에러 조용히 무시
+- ❌ 엔드포인트 하드코딩
+
+### 테스팅
+- ❌ 단언 없는 테스트
+- ❌ 에러 조건 테스트 생략
+- ❌ 엣지 케이스 무시
+- ❌ 구현 세부사항 테스트
+
+### 워크플로우
+- ❌ 린팅 없이 커밋
+- ❌ 테스트 없이 커밋
+- ❌ 깨진 코드 커밋
+- ❌ ESLint 경고 무시
+
+---
+
+## ✅ 항상 해야 할 사항 요약
+
+### 필수 패턴
+- ✅ 명시적 반환 타입 지정
+- ✅ 로딩/에러 상태 처리
+- ✅ 엣지 케이스 테스트
+- ✅ 의미 있는 변수명 사용
+- ✅ 접근성 속성 제공
+- ✅ JSDoc 주석 작성
+- ✅ AAA 패턴 테스트
+- ✅ 불변성 유지
+- ✅ 순수 함수 작성
+- ✅ 관심사 분리
+
+### 커밋 전 필수
+- ✅ `pnpm lint` 실행
+- ✅ `pnpm test` 실행
+- ✅ `pnpm build` 실행
+- ✅ TypeScript 에러 확인
+- ✅ 접근성 검증
+
+---
+
+## 📚 참고 파일 위치
+
+### 핵심 파일
+- 타입 정의: `src/types.ts`
+- 메인 컴포넌트: `src/App.tsx`
+- 진입점: `src/main.tsx`
+
+### 유틸리티
+- 날짜 계산: `src/utils/dateUtils.ts`
+- 이벤트 겹침: `src/utils/eventOverlap.ts`
+- 이벤트 유틸: `src/utils/eventUtils.ts`
+- 시간 검증: `src/utils/timeValidation.ts`
+- 알림 유틸: `src/utils/notificationUtils.ts`
+
+### 훅
+- 이벤트 폼: `src/hooks/useEventForm.ts`
+- 캘린더 뷰: `src/hooks/useCalendarView.ts`
+- 이벤트 작업: `src/hooks/useEventOperations.ts`
+- 알림: `src/hooks/useNotifications.ts`
+- 검색: `src/hooks/useSearch.ts`
+
+### 설정
+- TypeScript: `tsconfig.json`
+- ESLint: `eslint.config.js`
+- Vite: `vite.config.ts`
+- 패키지: `package.json`
+
+### 테스트
+- 테스트 설정: `src/setupTests.ts`
+- 목 핸들러: `src/__mocks__/handlers.ts`
+- 통합 테스트: `src/__tests__/medium.integration.spec.tsx`
+
+---
+
+## 💡 마지막 체크리스트
+
+코드를 작성할 때마다 다음을 확인하세요:
+
+1. [ ] 타입이 명시적으로 정의되어 있는가?
+2. [ ] 에러 처리가 구현되어 있는가?
+3. [ ] 접근성 속성이 있는가?
+4. [ ] 테스트가 작성되어 있는가?
+5. [ ] 함수가 50줄 이하인가?
+6. [ ] 코드가 중복되지 않았는가?
+7. [ ] 변수명이 의미 있는가?
+8. [ ] 불변성이 유지되는가?
+9. [ ] 성능이 고려되었는가?
+10. [ ] 린팅 경고가 없는가?
+
+**이 규칙들을 준수하면 고품질의 유지보수 가능한 코드를 작성할 수 있습니다!** 🎉
+
diff --git a/scripts/commit-stage.sh b/scripts/commit-stage.sh
new file mode 100755
index 00000000..cf3d3ec1
--- /dev/null
+++ b/scripts/commit-stage.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+FEATURE="$1" # 예: repeat-event
+STAGE="$2" # Architect | QA-RED | Dev-GREEN | Dev-REFACTOR | QA-VERIFY | CEO
+ROLE="$3" # Architect | QA | Dev | CEO
+DETAIL="${4:-}" # 선택 상세
+RUNLINE="${5:-}" # 선택 실행 로그 (예: "pnpm test, pnpm lint, pnpm build")
+
+case "$STAGE" in
+ Architect) TYPE="docs"; PREFIX="기능 명세"; OUT_FILE=".cursor/outputs/test-design-review.md" ;;
+ QA-RED) TYPE="test"; PREFIX="테스트 설계"; OUT_FILE=".cursor/outputs/test-design-review.md" ;;
+ Dev-GREEN) TYPE="feat"; PREFIX="기능 구현"; OUT_FILE=".cursor/outputs/code-implementation-review.md" ;;
+ Dev-REFACTOR) TYPE="refactor";PREFIX="코드 리팩토링"; OUT_FILE=".cursor/outputs/code-implementation-review.md" ;;
+ QA-VERIFY) TYPE="test"; PREFIX="테스트 통과"; OUT_FILE=".cursor/outputs/feature-verification-review.md" ;;
+ CEO) TYPE="chore"; PREFIX="승인 완료"; OUT_FILE=".cursor/outputs/ceo-approval.md" ;;
+ *) echo "[commit-stage] unknown STAGE: $STAGE"; exit 1 ;;
+ esac
+
+MSG="$TYPE: $PREFIX [by $ROLE] ($FEATURE)"
+if [ -n "$DETAIL" ]; then
+ MSG="$TYPE: $PREFIX - $DETAIL [by $ROLE] ($FEATURE)"
+fi
+
+git add .
+git commit -m "$MSG" || true
+HASH=$(git rev-parse --short HEAD)
+TS=$(date "+%F %T")
+
+if [ -n "$RUNLINE" ]; then
+ echo "commit: $HASH | $MSG | $TS | ran: $RUNLINE" >> "$OUT_FILE"
+else
+ echo "commit: $HASH | $MSG | $TS" >> "$OUT_FILE"
+fi
+
+echo "[commit-stage] committed: $HASH -> $MSG (logged to $OUT_FILE)"
diff --git a/scripts/new-cycle.sh b/scripts/new-cycle.sh
new file mode 100755
index 00000000..58151346
--- /dev/null
+++ b/scripts/new-cycle.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+FEATURE="$1" # 예: repeat-event
+DATE="${2:-$(date +%F)}" # 선택: 날짜 오버라이드
+
+mkdir -p .cursor/outputs
+
+copy_tmpl() {
+ local TMPL="$1"
+ local OUT="$2"
+ sed -e "s/{{FEATURE}}/${FEATURE}/g" \
+ -e "s/{{DATE}}/${DATE}/g" \
+ -e "s/{{PASS}}/0/g" \
+ -e "s/{{WARN}}/0/g" \
+ -e "s/{{EVIDENCE}}/TBD/g" \
+ -e "s/{{SUMMARY}}/TBD/g" \
+ -e "s/{{RUN_STATUS}}/TBD/g" \
+ ".cursor/templates/${TMPL}" > "${OUT}"
+}
+
+copy_tmpl "test-design-review.tmpl.md" ".cursor/outputs/test-design-review.md"
+copy_tmpl "test-code-review.tmpl.md" ".cursor/outputs/test-code-review.md"
+copy_tmpl "code-implementation-review.tmpl.md" ".cursor/outputs/code-implementation-review.md"
+copy_tmpl "feature-verification-review.tmpl.md" ".cursor/outputs/feature-verification-review.md"
+copy_tmpl "ceo-approval.tmpl.md" ".cursor/outputs/ceo-approval.md"
+
+echo "[new-cycle] initialized documents for feature: ${FEATURE} on ${DATE}"
diff --git a/src/App.tsx b/src/App.tsx
index 195c5b05..01209610 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,12 @@
-import { Notifications, ChevronLeft, ChevronRight, Delete, Edit, Close } from '@mui/icons-material';
+import {
+ Notifications,
+ ChevronLeft,
+ ChevronRight,
+ Delete,
+ Edit,
+ Close,
+ Repeat,
+} from '@mui/icons-material';
import {
Alert,
AlertTitle,
@@ -28,15 +36,14 @@ import {
Typography,
} from '@mui/material';
import { useSnackbar } from 'notistack';
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
import { useCalendarView } from './hooks/useCalendarView.ts';
import { useEventForm } from './hooks/useEventForm.ts';
import { useEventOperations } from './hooks/useEventOperations.ts';
import { useNotifications } from './hooks/useNotifications.ts';
import { useSearch } from './hooks/useSearch.ts';
-// import { Event, EventForm, RepeatType } from './types';
-import { Event, EventForm } from './types';
+import { Event, EventForm, RepeatType } from './types';
import {
formatDate,
formatMonth,
@@ -60,6 +67,14 @@ const notificationOptions = [
{ value: 1440, label: '1일 전' },
];
+const RepeatIconIfNeeded = ({
+ repeatType,
+ size = 'inherit',
+}: {
+ repeatType: RepeatType;
+ size?: 'small' | 'inherit';
+}) => (repeatType !== 'none' ? : null);
+
function App() {
const {
title,
@@ -77,11 +92,11 @@ function App() {
isRepeating,
setIsRepeating,
repeatType,
- // setRepeatType,
+ setRepeatType,
repeatInterval,
- // setRepeatInterval,
+ setRepeatInterval,
repeatEndDate,
- // setRepeatEndDate,
+ setRepeatEndDate,
notificationTime,
setNotificationTime,
startTimeError,
@@ -104,6 +119,41 @@ function App() {
const [isOverlapDialogOpen, setIsOverlapDialogOpen] = useState(false);
const [overlappingEvents, setOverlappingEvents] = useState([]);
+ const [isRepeatEditDialogOpen, setIsRepeatEditDialogOpen] = useState(false);
+ const [pendingEventData, setPendingEventData] = useState(null);
+ const [isRepeatDeleteDialogOpen, setIsRepeatDeleteDialogOpen] = useState(false);
+ const [pendingDeleteEvent, setPendingDeleteEvent] = useState(null);
+
+ const requestDelete = useCallback(
+ (event: Event) => {
+ if (event.repeat.type !== 'none') {
+ setPendingDeleteEvent(event);
+ setIsRepeatDeleteDialogOpen(true);
+ } else {
+ void deleteEvent(event.id);
+ }
+ },
+ [deleteEvent]
+ );
+
+ const handleConfirmSingleDelete = useCallback(async () => {
+ setIsRepeatDeleteDialogOpen(false);
+ if (pendingDeleteEvent) {
+ await deleteEvent(pendingDeleteEvent.id, { scope: 'single' });
+ setPendingDeleteEvent(null);
+ }
+ }, [pendingDeleteEvent, deleteEvent]);
+
+ const handleConfirmAllDelete = useCallback(async () => {
+ setIsRepeatDeleteDialogOpen(false);
+ if (pendingDeleteEvent) {
+ await deleteEvent(pendingDeleteEvent.id, {
+ scope: 'all',
+ repeatId: pendingDeleteEvent.repeat.id,
+ });
+ setPendingDeleteEvent(null);
+ }
+ }, [pendingDeleteEvent, deleteEvent]);
const { enqueueSnackbar } = useSnackbar();
@@ -135,6 +185,13 @@ function App() {
notificationTime,
};
+ // 편집 중인 반복 일정 저장 시: 단일/전체 선택 다이얼로그 노출
+ if (editingEvent && editingEvent.repeat.type !== 'none') {
+ setPendingEventData(eventData);
+ setIsRepeatEditDialogOpen(true);
+ return;
+ }
+
const overlapping = findOverlappingEvents(eventData, events);
if (overlapping.length > 0) {
setOverlappingEvents(overlapping);
@@ -145,6 +202,22 @@ function App() {
}
};
+ const handleConfirmSingleEdit = useCallback(async () => {
+ setIsRepeatEditDialogOpen(false);
+ if (pendingEventData) {
+ await saveEvent(pendingEventData as Event, { scope: 'single' });
+ setPendingEventData(null);
+ }
+ }, [pendingEventData, saveEvent]);
+
+ const handleConfirmAllEdit = useCallback(async () => {
+ setIsRepeatEditDialogOpen(false);
+ if (pendingEventData) {
+ await saveEvent(pendingEventData as Event, { scope: 'all' });
+ setPendingEventData(null);
+ }
+ }, [pendingEventData, saveEvent]);
+
const renderWeekView = () => {
const weekDates = getWeekDates(currentDate);
return (
@@ -201,6 +274,7 @@ function App() {
>
{isNotified && }
+
{isNotified && }
+
- {/* ! 반복은 8주차 과제에 포함됩니다. 구현하고 싶어도 참아주세요~ */}
- {/* {isRepeating && (
+ {isRepeating && (
- 반복 유형
+
+ 반복 유형
+
- )} */}
+ )}