From 4832f932e960c3d20086e6dad19a42cd9b1f776c Mon Sep 17 00:00:00 2001 From: Documentation Bot Date: Thu, 4 Jun 2026 23:04:17 +0000 Subject: [PATCH] docs: Sync documentation from private repository --- AGENTS.md | 201 +++++++++++++++++++++++++++++++++++++++++++ PROJECT_STRUCTURE.md | 68 ++++++++++++++- ROADMAP.md | 5 +- TECH_STACK.md | 152 +++++++++++++++++++++++++++++++- 4 files changed, 420 insertions(+), 6 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7ef2e1a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,201 @@ +# AGENTS.md + +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. + +--- + +## 프로젝트 개요 + +**라온누리 (Raonnuri)** - 초등학생 대상 창작 글쓰기 교육 플랫폼 + +**Tech Stack**: Next.js 16, React 19, Chakra UI v3, Firebase Auth, TypeScript + +--- + +## 개발 명령어 + +```bash +npm run dev # 개발 서버 (포트 3001, webpack 사용) +npm run build # 프로덕션 빌드 +npm run lint # ESLint 실행 +npx tsc --noEmit # 타입 체크 (빌드 전 필수) +``` + +**주의**: +- Dev server는 포트 3001 사용 +- React Compiler 활성화 +- 커밋 전 타입 체크 필수 (`npx tsc --noEmit`) + +--- + +## 핵심 아키텍처 + +### Manager Pattern (필수) + +**모든 데이터 작업은 Manager 사용 (서비스 직접 호출 금지)** + +```typescript +import { teamManager, userManager } from "@/managers"; + +// ✅ Good +const teams = await teamManager.getMyTeams(); +const users = await userManager.getUsersByTeam(teamId); + +// ❌ Bad - 서비스 직접 호출 금지 +``` + +**Available Managers**: +- `teamManager` - Team CRUD, member management, security level +- `userManager` - User CRUD +- `draftManager` - localStorage + Realtime DB 하이브리드 +- `writingSessionManager` - Real-time monitoring +- `writingManager`, `topicManager`, `feedManager` + +**상세**: `API_SPEC.md` 참조 + +--- + +### Data Model 핵심 원칙 + +**Firebase Auth = Single Source of Truth** (이름, 이메일, 사진) + +- Firestore Users 컬렉션: `uid`, `createdAt`, `lastLoginAt`, `settings`만 저장 +- UI User 객체: Firebase Auth + Firestore 자동 결합 +- 팀별 닉네임: `team.members[uid].nickname` (User 아님) +- 멤버 확인: `uid in team.members` + +**상세**: `DATA_MODELS.md` 참조 + +--- + +## 필수 개발 규칙 + +### 다국어 지원 (i18n) + +**모든 새로운 UI 및 페이지는 다국어 지원이 필수입니다.** + +```typescript +// ✅ Good +import {useTranslations} from "next-intl"; +import {useRouter} from "@/i18n/routing"; + +const t = useTranslations('newPage'); +return

{t('title')}

; + +// ❌ Bad +return

새 페이지

; // 하드코딩 금지! +``` + +**상세**: `I18N_GUIDE.md` 참조 + +--- + +### 스타일 가이드 + +**버튼에는 항상 `colorPalette="brand"` 사용**: + +```tsx +// ✅ Good + + +// ❌ Bad + // 기본 gray 사용 금지 +``` + +**Semantic 토큰 우선 사용** (숫자/하드코딩 금지): +- `color="fg"` / `color="fg.muted"` +- `bg="bg"` / `bg="brand.subtle"` +- `borderColor="border.muted"` + +**Icon 규칙**: react-icons 필수 (이모티콘 금지) +- ✅ `react-icons/lu` (Lucide) - 메인 아이콘 세트 +- ❌ 이모티콘 (🌍📝) - 사용 금지 + +**상세**: `STYLE_GUIDE.md` 참조 + +--- + +### API 개발 규칙 + +**RESTful 원칙**: HTTP Method로 동작 구분 + +``` +POST /api/resource → 생성/추가 +DELETE /api/resource → 삭제/제거 +PUT /api/resource → 전체 수정 +GET /api/resource → 조회 +``` + +**Firebase Admin SDK**: `adminFbClient` 싱글톤 사용 + +```typescript +// ✅ Good +import {adminFbClient} from "@/lib/firebase-admin"; +const doc = await adminFbClient.collection('users').doc(uid).get(); + +// ❌ Bad +import {getFirestore} from "firebase-admin/firestore"; +const db = getFirestore(); // 초기화 문제 발생 가능 +``` + +**API 응답**: 헬퍼 함수 필수 + +```typescript +import {successResponse, validationErrorResponse} from "@/lib/api-response"; + +// ✅ Good +return successResponse({ data }); +return validationErrorResponse("에러 메시지"); + +// ❌ Bad +return NextResponse.json({success: false, ...}); +``` + +**상세**: `API_SPEC.md` 참조 + +--- + +### 보안 + +**초대 링크 기반 팀 참여**: 디스코드 스타일 초대 시스템 사용 +- 모든 유저는 로그인 필수 (익명 인증 삭제됨) +- 팀 참여는 초대 링크를 통해서만 가능 +- 닫힌 팀 → 초대 안 만들면 됨 + +**XSS 방지**: 백엔드 자동 sanitize +- `src/lib/server/writing.ts`에서 모든 HTML 자동 세탁 +- 프론트엔드 별도 처리 불필요 + +**상세**: `SECURITY.md` 참조 + +--- + +## Documentation Requirements + +**모든 새 기능/페이지는 다음 3개 파일 업데이트 필수**: + +1. `PROJECT_STRUCTURE.md` - 페이지 구조, 컴포넌트 목록 +2. `ROADMAP.md` - 완료 작업, 예정 작업 +3. `TECH_STACK.md` - 아키텍처 패턴, 플로우 다이어그램 + +--- + +## 참조 문서 + +### 개발 가이드 +- **`API_SPEC.md`** - API 명세서, RESTful 원칙, Firebase Admin SDK +- **`STYLE_GUIDE.md`** - Color 사용법, Icon 규칙, Chakra UI 테마 +- **`FRONTEND_DESIGN_PATTERNS.md`** - 컴포넌트 디자인 철학, 인터랙션/시각/애니메이션 패턴 +- **`I18N_GUIDE.md`** - 다국어 지원 가이드 (필수) +- **`DEVELOPMENT_GUIDE.md`** - AI Delta 전송, Draft 저장, Firebase Functions + +### 프로젝트 문서 +- **`PROJECT_STRUCTURE.md`** - 프로젝트 구조 +- **`TECH_STACK.md`** - 기술 스택, 아키텍처 +- **`DATA_MODELS.md`** - 데이터베이스 스키마 +- **`SECURITY.md`** - 보안 정책, 3단계 보안 레벨 +- **`ROADMAP.md`** - 개발 로드맵 + +--- + +© 2024 BlueNovaLab. All rights reserved. \ No newline at end of file diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 9da81f3..28c644e 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -1,10 +1,25 @@ # 라온누리 - 프로젝트 구조 -> 최종 업데이트: 2026-03-27 (학생/교사 로그인 분리) +> 최종 업데이트: 2026-06-04 (스튜디오 플로우 UI 통합 리디자인) 초등학생을 위한 창작 글쓰기 교육 플랫폼 -**최신 업데이트** (2026-03-27): +**최신 업데이트** (2026-06-02): +- **스튜디오 6단계 선형 글쓰기 플로우** (`/write/studio`) - "그림으로 시작 → 글 완성"을 한 페이지에서 진행하는 신규 라우트 + - **단일 페이지 상태머신**: `page.tsx` 오케스트레이터(useReducer)가 단일 진실 공급원(StudioFlowState)을 보유, 7개 스테이지 컴포넌트를 순차 렌더 + - **6단계 흐름**: S0 안내(intro) → S1 업로드(upload) → S2 1차 작문(firstWriting) → S3 보상① 인터랙션(rewardInteraction) → S4 2차 작문(secondWriting) → S5 보상② 이펙트(rewardEffect) → S6 완료(complete) + - **S1 키워드 추출**: 종이 그림 촬영/업로드 → AI가 그림에서 키워드 5개 추출 (`POST /api/studio/analyze`) + - **S2 1차 작문**: 그림 + 키워드 5개 + 역할별 자유 3문장(상황/감정/이유, 한 문장씩 자유 작성) (학생 단독, AI 없음), 문장 구조는 플랫폼 비고정 — 선생님 직접 교육 + 역할별 예문·전구 안내로 지도(주제 연동 시 주제 데이터로 대체 예정), S2→S3 전환 시 draft Writing 생성(createWriting) + 이미지 업로드(uploadUserImage) + - **S3/S5 보상**: 공용 `RewardCanvasEditor`로 인터랙션(움직임) 1개 / 스프라이트 이펙트 1개 부여 (영역 1개 제한, `useDistortionEditor` 기반) + - **S4 글 다듬기**: AI 띄어쓰기 교정 + 의미 단위 낱말 분해 (`POST /api/studio/segment`) → 낱말 교체·자리 삽입 추천 (`POST /api/studio/suggest`), 낱말 칩 탭/+자리로 편집 (SentenceTokenEditor) + - **S6 완료**: 1회차 수업 종료 → draft를 발행(published)으로 마감 (`POST /api/studio/complete`) + - **공용 컴포넌트**: `StageStepper`(6단계 가로 진행 표시기, 점수/숫자 미표시), `RewardCanvasEditor`(인터랙션/이펙트 공용) + - **데이터**: writings 컬렉션에 `keywords`, `studioResult`(sentencesV1/V2, blanks, grantedInteraction/Effect, completedAt) 저장, 보상은 distortionAreas/spriteEffectAreas, status draft→published, rewardType `participation` + - **진입점**: `/write` 모드 선택 화면에 스튜디오 진입 카드 추가 + - **다국어 지원**: `studio` 네임스페이스, `pageTitles.writeStudio` (ko/en/ja) + - **레거시 공존**: 기존 글쓰기 라우트(`/write/fill-in-blank`, `/write/image`, `/write/easy`)와 병행 운영, 추후 제거 예정 + +**이전 업데이트** (2026-03-27): - **학생/교사 역할 분리** - 로그인/가입 시 학생/교사 탭 선택, 역할별 홈 페이지 분리 - **역할 시스템**: `UserRole` (student/teacher), Firebase Auth Custom Claims, Firestore `role` 필드 - **교사 승인 워크플로우**: 가입 → 승인 대기 → 관리자 승인/거절 → 로그인 가능 @@ -546,7 +561,8 @@ | **교사 승인 대기** | `/[locale]/teacher/pending-approval` | 교사 승인 대기 화면 | 승인 대기/거절 상태, 상태 확인, 로그아웃 | ✅ 완료 | | **내 글 모음** | `/[locale]/writings` | 🆕 **전체 글 목록 페이지** | 🆕 **사용자의 모든 글 표시 (Grid)**
🆕 **정렬 Select (최신순/오래된순)**
🆕 **WritingCard 컴포넌트 사용**
🆕 **Empty state 처리**
🆕 **전체 번역 완료** (ko/en/ja) | ✅ 완료 | | **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)
🆕 **SNS 공유 미리보기** (카카오톡/페이스북)
🆕 **서버 데이터 로딩** (Firebase Admin SDK)
제목, 내용, 생성된 이미지 표시
주제 및 팀 정보 Badge
프롬프트 Collapsible
🆕 **CommentList** (Client Component)
🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 | -| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **글 수정 기능 (URL params ?id=xxx)**
🆕 **수정 모드 배지 표시**
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
🆕 **저장 플로우 개편** (저장 → 백그라운드 분석 → `/imageUpload` 리다이렉트)
🆕 **GenerateImageDialog 제거** (새 플로우로 대체)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 | +| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **글 수정 기능 (URL params ?id=xxx)**
🆕 **수정 모드 배지 표시**
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
🆕 **저장 플로우 개편** (저장 → 백그라운드 분석 → `/imageUpload` 리다이렉트)
🆕 **GenerateImageDialog 제거** (새 플로우로 대체)
🆕 **스튜디오 진입 카드** (`/write/studio` 신규 플로우 진입)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 | +| **스튜디오 글쓰기** | `/[locale]/write/studio` | 🆕 **그림으로 시작하는 6단계 선형 글쓰기 플로우 (신규)** | 🆕 **단일 페이지 상태머신** (useReducer 오케스트레이터, StudioFlowState 단일 진실 공급원)
🆕 **6단계**: S0 안내 → S1 업로드(키워드 5개 추출) → S2 1차 작문(역할별 자유 3문장 + 예문·전구 안내, 학생 단독) → S3 보상① 인터랙션 → S4 글 다듬기(낱말 분해 + 교체·삽입 추천) → S5 보상② 이펙트 → S6 완료(발행)
🆕 **StageStepper** 진행 표시기 (점수/숫자 미표시)
🆕 **RewardCanvasEditor** 공용 (인터랙션/이펙트, 영역 1개 제한)
🆕 **AI 엔드포인트 4개**: analyze / segment / suggest / complete
🆕 **다국어 지원** (studio namespace, ko/en/ja)
레거시 라우트와 병행 운영 (추후 제거 예정) | ✅ 완료 | | **이미지 업로드/선택** | `/[locale]/imageUpload` | 🆕 **이미지 소스 선택 페이지 (글 저장 후 자동 이동)** | 🆕 **2가지 선택지**: AI 생성 (장면 추출 + 선택 + 생성) / 직접 업로드
🆕 **드래그앤드롭 파일 업로드** (미리보기, 5MB 제한, JPEG/PNG/WebP)
🆕 **Canvas API 클라이언트 사이드 리사이즈** (1920x1080 최대, 85% 품질)
🆕 **Firebase Storage 업로드** (WritingManager.uploadUserImage)
🆕 **이미 이미지 있으면 자동으로 `/interaction` 리다이렉트**
🆕 **다국어 지원** (imageUpload namespace, ko/en/ja) | ✅ 완료 | | **인터랙션 편집** | `/[locale]/interaction` | 🆕 **이미지 왜곡 편집 페이지 (이미지 생성/업로드 후 이동)** | 🆕 **7:3 그리드 레이아웃** (이미지 70%, 컨트롤러 30%)
🆕 **Sticky Header** (InteractionHeader, 스크롤 시 상단 고정)
🆕 **framer-motion 애니메이션** (모드 전환, 영역 선택 부드러운 전환)
🆕 **영역 목록 일반 모드 표시** (CustomAreaList, 모든 모드에서 접근 가능)
🆕 **에디터 버튼 우측 배치** (추가/삭제/숨기기, 가로 3개)
🆕 **컨트롤러 내부 스크롤** (커스텀 스크롤바, 높이 80dvh)
🆕 **반응형 이미지** (aspectRatio 유지, 찌그러짐 해결)
왜곡 영역 편집 (EditorCanvas, DistortionArea)
모션 프리셋 선택 (horizontal, vertical, rotate, pulse 등)
물리 설정 (stiffness, damping, mass)
에디터/인터랙션 모드 전환 (Switch)
저장 시 Writing.distortionAreas 업데이트 | ✅ 완료 | | **공개 팀 목록** | `/[locale]/team/all` | 🆕 **공개 팀 둘러보기** | 🆕 **공개된 팀 목록 표시** (isPublic=true)
🆕 **TeamCard 그리드** (글래스모피즘)
🆕 **페이지네이션** (커서 기반)
🆕 **Navbar "공개 팀" 메뉴** | ✅ 완료 | @@ -700,6 +716,7 @@ | **ColorMode** | `color-mode.tsx` | 다크모드 토글 | ✅ 완료 | | **Toaster** | `toaster.tsx` | 토스트 알림 | ✅ 완료 | | **Tooltip** | `tooltip.tsx` | 툴팁 | ✅ 완료 | +| **StageStepper** | `StageStepper.tsx` | 🆕 **스튜디오 6단계 가로 진행 표시기** (현재/완료 단계 brand 강조, 점수·숫자 미표시, 모바일은 점만 표시) | ✅ 완료 | --- @@ -729,6 +746,15 @@ | **VisibilitySelector** | `VisibilitySelector.tsx` | 🆕 **공개 범위 선택** (PUBLIC/TEAM/PRIVATE, RadioCard 기반) | ✅ 완료 | | **VisibilityBadge** | `VisibilityBadge.tsx` | 🆕 **공개 범위 배지** (아이콘 + 라벨, 색상별 구분) | ✅ 완료 | | **InteractiveImageViewer** | `InteractiveImageViewer.tsx` | 🆕 **인터랙티브 이미지 뷰어** (왜곡 효과, 애니메이션, 오디오 반응) | ✅ 완료 | +| **RewardCanvasEditor** | `studio/RewardCanvasEditor.tsx` | 🆕 **스튜디오 보상 편집기** (인터랙션 S3 / 이펙트 S5 공용, `useDistortionEditor` 기반, 영역 1개 제한, onApplied 콜백) | ✅ 완료 | +| **StudioCard** | `studio/StudioCard.tsx` | 🆕 **스튜디오 공용 콘텐츠 카드** (tone default/muted/brand, cascade index, 단계 전반 통일 카드) | ✅ 완료 | +| **StudioStageHeader** | `studio/StudioStageHeader.tsx` | 🆕 **스튜디오 단계 헤더** (원형 아이콘 + 제목 + 부제, 전 단계 공용) | ✅ 완료 | +| **RolePill** | `studio/RolePill.tsx` | 🆕 **역할 식별 Pill** (상황/마음/까닭 아이콘+라벨, S2·S4 공용, `studioRoleMeta.ts` 메타 기반) | ✅ 완료 | +| **StudioImageCard** | `studio/StudioImageCard.tsx` | 🆕 **고정 비율 이미지 카드** (선택적 children, 예: 키워드 칩) | ✅ 완료 | +| **RewardBanner** | `studio/RewardBanner.tsx` | 🆕 **보상 축하 배너** (S3/S5 보상 단계 공용) | ✅ 완료 | +| **SentenceTokenEditor** | `studio/SentenceTokenEditor.tsx` | 🆕 **S4 낱말 칩 편집기** (낱말 탭→교체 Popover, +자리→삽입 Popover, 갱신 낱말 그라디언트, `sentenceTokens.ts` 조립/폴백 유틸) | ✅ 완료 | +| **studioMotion** | `studio/studioMotion.ts` | 🆕 **스튜디오 공용 애니메이션** (studioFadeIn/Pop/Breathe/EmptyBreathe @keyframes + STUDIO_REDUCED_MOTION prefers-reduced-motion 게이트) | ✅ 완료 | +| **studioRoleMeta** | `studio/studioRoleMeta.ts` | 🆕 **역할 메타 정의** (상황/마음/까닭 아이콘·라벨, RolePill 공용) | ✅ 완료 | | **ImageDropzone** | `ImageDropzone.tsx` | 🆕 **범용 이미지 드롭존** (드래그앤드롭 업로드, 미리보기, 4:3 크롭 통합, 호버 오버레이, 삭제 버튼, AspectRatio 지원, 커스텀 라벨 — 글쓰기/팀 커버 이미지 공용) | ✅ 완료 | | **ImageCropper** | `ImageCropper.tsx` | 🆕 **이미지 크롭 모달** (react-cropper 기반, 4:3 고정 비율, 회전/확대/축소, 최소 400x300) | ✅ 완료 | | **CameraCaptureDialog** | `CameraCaptureDialog.tsx` | 🆕 **카메라 촬영 다이얼로그** (GlassDialog 기반, getUserMedia 카메라 스트림, 4:3 프레임 가이드 오버레이, 전/후면 카메라 전환, 촬영 후 ImageCropper 연동, 권한/미지원 에러 처리, 다국어 지원) | ✅ 완료 | @@ -847,6 +873,31 @@ --- +### 📁 `src/app/[locale]/write/studio/` - 🆕 스튜디오 6단계 플로우 + +| 파일 | 경로 | 설명 | 상태 | +|------|------|------|------| +| **StudioPage** | `page.tsx` | 🆕 **오케스트레이터** (useReducer 상태머신, StudioFlowState 단일 진실 공급원, goNext 부수효과 처리, AI 호출, draft 생성/발행) | ✅ 완료 | +| **Flow 계약** | `_flow/types.ts` | 🆕 **StudioFlowState / StudioStageProps** (오케스트레이터↔스테이지 prop 시그니처), `deriveBlankValues` | ✅ 완료 | +| **IntroStage** | `_stages/IntroStage.tsx` | 🆕 **S0 안내** (오프라인에서 종이에 그림 그리기 안내) | ✅ 완료 | +| **UploadStage** | `_stages/UploadStage.tsx` | 🆕 **S1 업로드** (그림 촬영/업로드 → AI 키워드 5개 추출) | ✅ 완료 | +| **FirstWritingStage** | `_stages/FirstWritingStage.tsx` | 🆕 **S2 1차 작문** (그림 + 키워드 5개 + 역할별 자유 3문장 + 전구 안내 + 예문, 학생 단독·AI 없음) | ✅ 완료 | +| **RewardInteractionStage** | `_stages/RewardInteractionStage.tsx` | 🆕 **S3 보상①** (인터랙션/움직임 1개 부여, RewardCanvasEditor) | ✅ 완료 | +| **SecondWritingStage** | `_stages/SecondWritingStage.tsx` | 🆕 **S4 글 다듬기** (낱말 분해 + 교체·삽입 추천, SentenceTokenEditor) | ✅ 완료 | +| **RewardEffectStage** | `_stages/RewardEffectStage.tsx` | 🆕 **S5 보상②** (스프라이트 이펙트 1개 부여, RewardCanvasEditor) | ✅ 완료 | +| **CompletionStage** | `_stages/CompletionStage.tsx` | 🆕 **S6 완료** (1회차 수업 종료, complete API 호출 → 발행) | ✅ 완료 | + +**주요 기능**: +- ✅ **단일 페이지 선형 상태머신** (useReducer, 단계 전환 부수효과는 오케스트레이터 goNext에서 처리) +- ✅ **역할별 자유 3문장**: 상황/감정/이유를 한 문장씩 자유 작성 (고정 빈칸 템플릿 제거, 선생님 직접 교육 + 예문·전구 안내로 지도) +- ✅ **S2→S3 전환**: 모든 입력 채움 검증 → draft Writing 생성(createWriting) + 이미지 업로드(uploadUserImage), 1차 문장을 2차 작문 초기값으로 시드 +- ✅ **S3/S5 보상**: 공용 RewardCanvasEditor (영역 1개 제한), 적용 시 distortionAreas/spriteEffectAreas 저장 후 자동 전진 +- ✅ **AI 엔드포인트 4개**: `/api/studio/analyze`(키워드), `/api/studio/segment`(S4 분해), `/api/studio/suggest`(S4 교체·삽입 추천), `/api/studio/complete`(발행) +- ✅ **데이터 저장**: writings.keywords + writings.studioResult (sentencesV1/V2, blanks, grantedInteraction/Effect, completedAt), status draft→published, rewardType `participation` +- ✅ **다국어 지원** (studio namespace, pageTitles.writeStudio, ko/en/ja) + +--- + ### 📁 `src/extensions/` - Tiptap Extensions | Extension | 파일명 | 설명 | 상태 | @@ -1065,6 +1116,7 @@ firebase functions:log --only cleanupExpiredReservations | **Pattern Analysis** | `patternAnalysisService.ts` | 🆕 **글 작성 패턴 분석 서비스** (10개 글 종합 분석, AI 평가) | ✅ 완료 | | **Spelling Check** | `spellingService.ts` | **맞춤법 검사 서비스** (Gemini 기반, 초등학생 눈높이) | ✅ 완료 | | **Writing Assistance** | `writingAssistanceService.ts` | 🆕 **AI 글쓰기 도우미 서비스** (4단계 힌트 생성, 주제 맥락 활용, 서버 캐싱) | ✅ 완료 | +| **Studio Service** | `studioService.ts` | 🆕 **스튜디오 플로우 AI 서비스** (extractStudioKeywords: 그림→키워드 5개, segmentSentences: 띄어쓰기 교정+낱말 분해, suggestReplacements: 교체·삽입 추천, _ai 사용량 메타 전달) | ✅ 완료 | | **Region Health** | `regionHealthManager.ts` | **Region 과부하 상태 추적** (자동 복구, 1분 TTL) | ✅ 완료 | | ~~**Team Service**~~ | ~~`teamService.ts`~~ | ~~팀 CRUD~~ | ⚠️ Deprecated (TeamManager로 이동) | | ~~**Student Service**~~ | ~~`studentService.ts`~~ | ~~학생 CRUD, PIN 해시/검증~~ | ⚠️ Deprecated (UserManager로 대체) | @@ -1085,7 +1137,8 @@ firebase functions:log --only cleanupExpiredReservations | **Plan 타입** | `plan.ts` | 🆕 **플랜 시스템 타입** (PlanType/BillingCycle/PlanSource/AIFeatureType Enum, UserPlan, Organization, AIUsage, PlanLimits, EffectivePlan, AIFeatureCheckResult) | ✅ 완료 | | **Team 타입** | `team.ts` | 팀 데이터 모델 (members Map), 🆕 **AIAssistanceConfig** (AI 도우미 설정) | ✅ 완료 | | **FirestoreUser 타입** | `firestoreUser.ts` | **FirestoreUser** (DB 저장용, 🆕 **plan/organizationId 필드 추가**, 🆕 **loginId/isSystemAccount/createdByTeacher 필드**), **User** (UI용) 분리 | ✅ 완료 | -| **Writing 타입** | `writing.ts` | 글 데이터 모델, 🆕 **WritingAnalysis** (AI 분석 결과, contentHash 기반 재사용), **SpellingError** (맞춤법 오류), 🆕 **AIAssistanceRecord** (AI 도움 이력), 🆕 **GeneratedImage** (AI 생성 이미지) | ✅ 완료 | +| **Writing 타입** | `writing.ts` | 글 데이터 모델, 🆕 **WritingAnalysis** (AI 분석 결과, contentHash 기반 재사용), **SpellingError** (맞춤법 오류), 🆕 **AIAssistanceRecord** (AI 도움 이력), 🆕 **GeneratedImage** (AI 생성 이미지), 🆕 **keywords?·studioResult?** (스튜디오 플로우 필드) | ✅ 완료 | +| **Studio 타입** | `studio.ts` | 🆕 **스튜디오 6단계 플로우 타입** (StudioStage, StudioSentenceKey, StudioBlank, StudioScaffold, StudioToken, StudioResult, Analyze/Segment/Suggest/Complete Request·Response, STUDIO_STAGE_ORDER/SENTENCE_ORDER) | ✅ 완료 | | **Scene 타입** | `scene.ts` | 🆕 **장면 데이터 모델** (Scene, SceneExtractionResponse) | ✅ 완료 | | **Draft 타입** | `draft.ts` | 글조각 데이터 모델 (Draft, DraftListItem, **AnalysisHistoryItem**, **syncStatus**: 'local'\|'synced'\|'syncing') | ✅ 완료 | | **WritingPattern 타입** | `writingPattern.ts` | **글 작성 패턴 분석** 데이터 모델 (WritingPatternAnalysis) | ✅ 완료 | @@ -1146,6 +1199,9 @@ firebase functions:log --only cleanupExpiredReservations | **Writing Assistance** | `writingAssistance.ts` | 🆕 **AI 글쓰기 도우미 프롬프트** (4단계 × 3개 언어, 주제 맥락 활용) | ✅ 완료 | | **Scene Extraction** | `sceneExtraction.ts` | 🆕 **AI 장면 추출 프롬프트** (3~5개 장면 분리, Response Schema, ko/en/ja) | ✅ 완료 | | **Prompt Optimization** | `promptOptimization.ts` | 🆕 **프롬프트 최적화 프롬프트** (원문 → 핵심 키워드 배열 추출, Imagen 최적화) | ✅ 완료 | +| **Studio Templates** | `studio/templates.ts` | 🆕 **스튜디오 역할별 자유 슬롯** (상황/감정/이유 1슬롯씩, STUDIO_BLANK_IDS, getStudioBlanks, composeStudioSentences, studioBlankId) | ✅ 완료 | +| **Studio Segmentation** | `studio/segmentation.ts` | 🆕 **S4 분해 프롬프트** (의미 단위 낱말 덩어리 + 띄어쓰기 교정, ko/en/ja) | ✅ 완료 | +| **Studio Suggestion** | `studio/suggestion.ts` | 🆕 **S4 추천 프롬프트** (낱말 교체 + 자리 삽입 추천, ko/en/ja) | ✅ 완료 | --- @@ -1157,6 +1213,10 @@ firebase functions:log --only cleanupExpiredReservations | **패턴 분석** | `/api/analyze-pattern` | POST | **글 작성 패턴 분석** (3가지 타입, contentHash 기반 3단계 캐싱, 변경 감지) | ✅ 완료 | | **맞춤법 검사** | `/api/spelling/check` | POST | **Gemini 기반 맞춤법 검사** (초등학생 눈높이) | ✅ 완료 | | **AI 글쓰기 도우미** | `/api/writing-assistance` | POST | 🆕 **AI 힌트 생성** (4단계, 주제 맥락, 팀 설정 검증) | ✅ 완료 | +| **스튜디오 그림 분석** | `/api/studio/analyze` | POST | 🆕 **S1 그림 키워드 추출** (analyzeImage 재사용, 키워드 5개 + 분석 결과, 플랜 검증) | ✅ 완료 | +| **스튜디오 분해** | `/api/studio/segment` | POST | 🆕 **S4 띄어쓰기 교정 + 낱말 분해** (의미 단위 낱말 덩어리, 무결성 가드) | ✅ 완료 | +| **스튜디오 추천** | `/api/studio/suggest` | POST | 🆕 **S4 교체·삽입 추천** (낱말 교체 + 자리 삽입, Response Schema) | ✅ 완료 | +| **스튜디오 완료** | `/api/studio/complete` | POST | 🆕 **S6 발행** (draft→published, studioResult 저장, HTML sanitize, AI 호출 없음, 주제 사용량 증가) | ✅ 완료 | | **글 생성** | `/api/writing` | POST | 🆕 **글 생성** (wordCount/charCount 자동 계산, 🆕 **analysis 저장**) | ✅ 완료 | | **글 조회** | `/api/writing/[id]` | GET | 🆕 **글 조회** (작성자만 접근) | ✅ 완료 | | **글 분석** | `/api/writing/[id]/analyze` | POST | 🆕 **글 분석 실행** (서버 데이터 기반, 결과 저장) | ✅ 완료 | diff --git a/ROADMAP.md b/ROADMAP.md index ded7d4e..6281171 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # 라온누리 - 개발 로드맵 -> 최종 업데이트: 2026-03-27 (학생/교사 로그인 분리) +> 최종 업데이트: 2026-06-04 (S2 첫 글쓰기 자유 작성 전환) 초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획 @@ -167,6 +167,9 @@ | **학생 일괄 생성 (Bulk Student Creation)** | **CSV 기반 학생 계정 일괄 생성, 템플릿 ID/PW 시스템 ({teamCode}{number} 등), loginId 간편 로그인 (@ 없는 ID → 시스템 이메일 변환), BulkMemberCreator 4단계 UI (입력→미리보기→생성중→결과), 로그인 카드 인쇄 기능, POST /api/team/[teamId]/bulk-create-members (소유자 전용), POST /api/auth/resolve-login-id, 서버 레이어 bulk-member.ts, FirestoreUser loginId/isSystemAccount/createdByTeacher 필드 추가, authStore loginId 로그인 지원, LoginForm 이메일/loginId 겸용, 다국어 지원 (ko/en/ja)** | **2026-03-19** | | **학생/교사 로그인 분리** | **UserRole (student/teacher) 타입, Custom Claims 기반 역할 관리, LoginDialog 학생/교사 탭 UI, 학생/교사 별도 SignupForm, 교사 승인 워크플로우 (가입→대기→관리자 승인/거절), withTeacherAuth/withAdminAuth 서버 래퍼, 역할별 홈 페이지 분리 (/student/home, /teacher/home), useRequireAuth requiredRole 옵션, 관리자 교사 승인 UI (/admin/teacher-approvals), TeacherApprovalManager 싱글톤, 마이그레이션 스크립트 (기존 유저 → student)** | **2026-03-27** | +| **스튜디오 6단계 선형 글쓰기 플로우** | **"그림으로 시작 → 글 완성" 신규 라우트 /write/studio (기존 fill-in-blank/image/easy 라우트와 병행 운영, 추후 제거 예정), 단일 페이지 상태머신 (page.tsx 오케스트레이터 useReducer, StudioFlowState 단일 진실 공급원, _flow/types.ts에 StudioStageProps 계약 정의), 7개 스테이지 컴포넌트 (_stages/Intro·Upload·FirstWriting·RewardInteraction·SecondWriting·RewardEffect·CompletionStage), StageStepper 진행 표시기 (점수·숫자 미표시, 참여 기반), 6단계: S0 안내(종이 그림)→S1 업로드(AI 키워드 5개 추출)→S2 1차 작문(3문장 빈칸: 상황 2칸/감정/이유, 학생 단독 AI 없음)→S3 보상① 인터랙션→S4 2차 작문(AI 추천 표현 종류별)→S5 보상② 이펙트→S6 완료(발행), S2→S3 전환 시 draft Writing 생성(createWriting)+이미지 업로드(uploadUserImage), 공용 RewardCanvasEditor (인터랙션/이펙트 공용, 영역 1개 제한, useDistortionEditor 기반), AI 엔드포인트 3개 (POST /api/studio/analyze·recommend-expressions·complete), studioService.ts (extractStudioKeywords·recommendExpressions), 프롬프트 studio/templates.ts·expressionRecommendation.ts, 타입 studio.ts (StudioStage/SentenceKey/Blank/Result 등), writing.ts에 keywords?·studioResult? 추가, writings 컬렉션에 keywords·studioResult(sentencesV1/V2·blanks·grantedInteraction/Effect·completedAt) 저장, 보상은 distortionAreas/spriteEffectAreas, status draft→published, rewardType participation, /write 진입 카드 추가, 다국어 지원 (studio namespace, pageTitles.writeStudio, ko/en/ja)** | **2026-06-02** | +| **스튜디오 플로우 UI 통합 리디자인** | **공용 프리미티브 + 단계별 일관 레이아웃, S2 빈칸 입력 버그 수정 (src/components/writing/studio/에 표현 전용 공용 프리미티브 정리: StudioCard·StudioStageHeader·RolePill+studioRoleMeta·FillBlankSentence·StudioImageCard·RewardBanner·ExpressionChip·studioMotion, 전 단계가 하나의 카드/헤더/애니메이션 시스템 공유, FillBlankSentence 고정 폭 빈칸 입력 + 빈칸별 힌트로 입력 늘어남 버그 수정, studio namespace i18n 키 추가 roles.{situation,emotion,reason}·firstWriting.blankStub·rewardInteraction.tip·rewardEffect.tip·reward.{controlsTitle,dragHint,applyButton}·secondWriting.contextTitle, ko/en/ja)** | **2026-06-04** | +| **S2 첫 글쓰기 자유 작성 전환** | **고정 빈칸 템플릿("나는 ___ 때 ___ 일이 있었습니다" 등) 제거 → 선생님 직접 교육 방식. 역할(상황/감정/이유)별 자유 Textarea 1문장 + 전구 안내(guides) + 예문 2개(examples)를 FirstWritingStage가 직접 렌더, FillBlankSentence 컴포넌트 삭제, templates.ts 토큰 템플릿 제거하고 역할당 1슬롯 모델로 단순화 (STUDIO_BLANK_IDS 3개·getStudioBlanks·composeStudioSentences(values)·studioBlankId), StudioTemplateToken/StudioSentenceTemplate 타입 제거, 다운스트림(sentencesV1/V2·S4 분해) 불변, 예문/안내는 현재 i18n 하드코딩(주제 연동 시 주제 데이터로 변수화 예정), firstWriting i18n 재구성 freePlaceholder·examplesLabel·guides.{situation,emotion,reason}·examples.{role}.{0,1} (ko/en/ja). 부수: TECH_STACK 스튜디오 섹션을 이미 반영된 S4 segment/suggest 파이프라인 기준으로 정정** | **2026-06-04** | ### 🚧 진행 중 diff --git a/TECH_STACK.md b/TECH_STACK.md index 2d7dc7c..57138ae 100644 --- a/TECH_STACK.md +++ b/TECH_STACK.md @@ -1,6 +1,6 @@ # 라온누리 - 기술 스택 및 개발 환경 -> 최종 업데이트: 2025-12-23 (이미지 4:3 비율 표준화) +> 최종 업데이트: 2026-06-04 (스튜디오 플로우 UI 통합 리디자인) --- @@ -2638,6 +2638,156 @@ interface BulkCreateResult { --- +### 27. 스튜디오 6단계 선형 글쓰기 플로우 (Studio Flow) + +#### 핵심 개념 + +**목적**: "그림으로 시작 → 글 완성"을 하나의 페이지에서 진행하는 1회차 수업형 선형 플로우. +기존 글쓰기 라우트(`/write/fill-in-blank`, `/write/image`, `/write/easy`)와 **병행 운영**하는 신규 라우트(`/write/studio`)이며, 레거시 라우트는 추후 제거 예정이다. + +**설계 원칙**: +- **점수/제한 없음**: 참여 기반 플로우 (StageStepper에 숫자 미표시, `rewardType: 'participation'`) +- **AI는 아동 행동 이후에만**: S1 키워드 추출은 업로드 후, S4 추천 표현은 학생 1차 작문 후 실행 +- **단일 진실 공급원**: 오케스트레이터가 전역 상태(StudioFlowState)를 소유, 스테이지는 props만 받는다 + +#### 단일 페이지 상태머신 아키텍처 + +``` +┌──────────────────────────────────────────────────────────┐ +│ 오케스트레이터 src/app/[locale]/write/studio/page.tsx │ +│ - useReducer(flowReducer, StudioFlowState) ← 단일 진실 │ +│ - goNext(): 현재 단계별 부수효과 실행 후 advance() │ +│ - AI 호출 / draft 생성 / 보상 저장 / 발행 모두 여기서 처리 │ +│ - StageStepper(현재 단계) + AnimatePresence(스테이지 전환) │ +└──────────────────────────────────────────────────────────┘ + │ Pick 으로 필요한 props만 전달 + ▼ +┌──────────────────────────────────────────────────────────┐ +│ 스테이지 컴포넌트 src/app/[locale]/write/studio/_stages/ │ +│ IntroStage · UploadStage · FirstWritingStage · │ +│ RewardInteractionStage · SecondWritingStage · │ +│ RewardEffectStage · CompletionStage │ +│ (상태/부수효과 없음 — 입력 수집 + 콜백 호출만) │ +└──────────────────────────────────────────────────────────┘ + │ 보상 단계(S3/S5)는 공용 편집기 사용 + ▼ +┌──────────────────────────────────────────────────────────┐ +│ 공용 컴포넌트 │ +│ src/components/ui/StageStepper.tsx (6단계 진행 표시기) │ +│ src/components/writing/studio/RewardCanvasEditor.tsx │ +│ - mode: "interaction" | "effect" 로 S3/S5 공용 │ +│ - useDistortionEditor 기반, 영역 1개 제한 (AREA_CAP=1) │ +└──────────────────────────────────────────────────────────┘ +``` + +**계약 파일**: `src/app/[locale]/write/studio/_flow/types.ts` — `StudioFlowState`(전역 상태) / `StudioStageProps`(스테이지 prop 시그니처) / `deriveBlankValues()` 헬퍼. + +#### S0 → S6 단계 흐름 + +``` +S0 intro 오프라인에서 종이에 그림 그리기 안내 (앱 외부 활동) + ↓ goNext() +S1 upload 그림 촬영/업로드 → AI 키워드 5개 추출 + ↓ runAnalyze() → POST /api/studio/analyze +S2 firstWriting 그림 + 키워드 5개 + 역할별 자유 3문장 (학생 단독, AI 없음) + · situation: 상황 — 언제/어디서 무슨 일이 있었는지 (자유 1문장) + · emotion: 감정 — 그때 어떤 마음이었는지 (자유 1문장) + · reason: 이유 — 왜 그런 마음이 들었는지 (자유 1문장) + · 문장 구조를 플랫폼이 고정하지 않음(선생님 직접 교육). 역할별 예문 + 전구 안내로만 지도. 주제 연동 시 예문/안내는 주제 데이터로 대체 예정(현재 i18n 하드코딩) + ↓ createDraft(): 입력 검증 → writingManager.createWriting(status:'draft') + ↓ + uploadUserImage() + 1차 문장을 2차 작문 초기값으로 시드 +S3 rewardInteraction 인터랙션(움직임) 1개 부여 → 적용 (RewardCanvasEditor, 영역 1개) + ↓ onInteractionApplied() → updateDistortionAreas() +S4 secondWriting 3문장 다듬기 — AI 띄어쓰기 교정 + 낱말 분해 → 낱말 교체·자리 삽입 추천 (낱말 칩 편집) + ↓ 진입 시 fetchSegmentation() → POST /api/studio/segment (분해 후) fetchSuggestions() → POST /api/studio/suggest +S5 rewardEffect 스프라이트 이펙트 1개 부여 → 적용 (RewardCanvasEditor, 영역 1개) + ↓ onEffectApplied() → updateDistortionAreas() +S6 complete 1회차 수업 종료 → onComplete() → POST /api/studio/complete (발행) +``` + +#### AI 엔드포인트 4개 + +| API | 경로 | AI | 역할 | +|-----|------|----|------| +| **분석** | `POST /api/studio/analyze` | ✅ | 그림 분석(analyzeImage 재사용) → 키워드 5개 + ImageAnalysisResult | +| **분해** | `POST /api/studio/segment` | ✅ | S4: 3문장 → 띄어쓰기 교정 + 의미 단위 낱말 분해 (낱말 텍스트 그대로, 글자 변조 금지 가드) | +| **추천** | `POST /api/studio/suggest` | ✅ | S4: 분해된 낱말 → 낱말 교체 추천 + 자리 삽입 추천 (분해와 분리된 별도 호출) | +| **완료** | `POST /api/studio/complete` | ❌ | draft→published 발행, studioResult 저장, HTML sanitize | + +**서비스 레이어**: `src/services/studioService.ts` +- `extractStudioKeywords(imageBase64, mimeType, locale)` — imageAnalysisService.analyzeImage 재사용, suggestedKeywords→(부족 시)objects 순으로 정확히 5개 확정 (패딩 없음) +- `segmentSentences(sentences, locale)` — AI가 의미 단위 낱말 덩어리로 반환 → 결정적 분해/조립, `stripSpaces` 무결성 가드 통과 시 채택, 실패 시 공백 분리 폴백 +- `suggestReplacements(sentences, keywords, locale)` — generateContent + Response Schema, 문장별 `{replacements, insertions}`(교체·삽입) 클램프/정제, 실패 시 빈 추천 폴백 +- 사용량 메타데이터(`_ai`)는 라우트로 그대로 전달 → 라우트가 logAIUsage 기록 + +**프롬프트**: `src/prompts/studio/templates.ts`(역할별 자유 슬롯 — STUDIO_BLANK_IDS/getStudioBlanks/composeStudioSentences/studioBlankId), `src/prompts/studio/segmentation.ts`·`suggestion.ts`(ko/en/ja) + +#### 데이터 / 영속화 모델 + +draft를 먼저 만들고(S2→S3) 단계별로 갱신한 뒤 S6에서 발행하는 점진적 저장 모델. + +```typescript +// src/types/writing.ts (스튜디오 모드에서만 사용) +interface Writing { + // ...기존 필드 + keywords?: string[]; // 🆕 그림에서 추출한 키워드 5개 + studioResult?: StudioResult; // 🆕 스튜디오 플로우 결과 +} + +// src/types/studio.ts +interface StudioResult { + keywords: string[]; + sentencesV1: Record; // 1차 작문 (학생 단독) + sentencesV2: Record; // 2차 작문 (AI 도움, 최종 본문) + blanks: StudioBlank[]; + grantedInteraction: boolean; // S3 보상 적용 여부 + grantedEffect: boolean; // S5 보상 적용 여부 + completedAt: string; // ISO 8601 +} +``` + +**저장 위치별 정리**: +- **writings 컬렉션**: `keywords`, `studioResult`(sentencesV1/V2·blanks·grantedInteraction/Effect·completedAt) +- **보상**: 인터랙션/이펙트는 `distortionAreas` / `spriteEffectAreas` (S3/S5에서 클라이언트가 저장, complete API는 건드리지 않음) +- **상태 전이**: `status` draft → published (최초 발행 시 주제 usageCount 1회 증가) +- **보상 분류**: `rewardType: 'participation'` +- **하드닝**: complete API에서 본문 HTML sanitize + studioResult 자유 입력값은 태그 제거 후 평문 저장 + +#### 진입점 및 i18n + +- **진입 카드**: `/write` 모드 선택 화면 상단에 스튜디오 진입 카드 추가 (`router.push("/write/studio")`) +- **다국어**: `studio` 네임스페이스(stepper/actions/error/단계별 키), `pageTitles.writeStudio` (ko/en/ja) +- **접근 제어**: `useRequireAiAccess()` + `useAiIpCheck()` (AI 사용 가능 검증) + +#### 공용 프리미티브 / 통합 비주얼 언어 (2026-06-04) + +전 단계가 **하나의 카드/헤더/애니메이션 시스템**을 공유하도록 `src/components/writing/studio/`에 표현 전용(presentational) 공용 프리미티브를 정리했다. 스테이지는 레이아웃 코드를 직접 들지 않고 이 프리미티브만 조합한다. + +- **`StudioCard`** — 단계 공용 콘텐츠 카드 (tone default/muted/brand, cascade index 진입 애니메이션) +- **`StudioStageHeader`** — 원형 아이콘 + 제목 + 부제 헤더 (전 단계 통일) +- **`RolePill` + `studioRoleMeta`** — 상황/마음/까닭 역할 식별(아이콘+라벨), S2·S4 공용 +- **`StudioImageCard`** — 고정 비율 이미지 카드 (선택적 children, 예: 키워드 칩) +- **`RewardBanner`** — S3/S5 보상 축하 배너 공용 +- **`SentenceTokenEditor` + `sentenceTokens`** — S4 낱말 칩 편집기 (낱말 탭→교체 Popover, +자리→삽입 Popover, 갱신 낱말 그라디언트 표시) + 토큰 조립/폴백 유틸 +- **`studioMotion`** — 공용 `@keyframes`(studioFadeIn/Pop/Breathe/EmptyBreathe) + `STUDIO_REDUCED_MOTION`(prefers-reduced-motion 게이트) +- **S2 자유 작성**: 고정 빈칸 템플릿(FillBlankSentence) 제거 → FirstWritingStage가 역할별 자유 Textarea + 전구 안내 + 예문을 직접 렌더 +- **i18n 추가**: `studio` 네임스페이스에 `roles.{situation,emotion,reason}`, `firstWriting.{freePlaceholder,examplesLabel,guides.*,examples.*}`, `rewardInteraction.tip`, `rewardEffect.tip`, `reward.{controlsTitle,dragHint,applyButton}`, `secondWriting.*`(낱말 편집 키) (ko/en/ja) + +#### 참고 파일 + +- `src/app/[locale]/write/studio/page.tsx` - 오케스트레이터 (useReducer 상태머신) +- `src/app/[locale]/write/studio/_flow/types.ts` - StudioFlowState / StudioStageProps +- `src/app/[locale]/write/studio/_stages/*.tsx` - 7개 스테이지 컴포넌트 +- `src/components/ui/StageStepper.tsx` - 6단계 진행 표시기 +- `src/components/writing/studio/RewardCanvasEditor.tsx` - 인터랙션/이펙트 공용 편집기 +- `src/components/writing/studio/{StudioCard,StudioStageHeader,RolePill,StudioImageCard,RewardBanner,SentenceTokenEditor}.tsx` + `{sentenceTokens,studioMotion,studioRoleMeta}.ts` - 공용 프리미티브 (통합 비주얼 언어) +- `src/services/studioService.ts` - extractStudioKeywords / segmentSentences / suggestReplacements +- `src/app/api/studio/{analyze,segment,suggest,complete}/route.ts` - AI 엔드포인트 4개 +- `src/prompts/studio/{templates.ts,segmentation.ts,suggestion.ts}` - 프롬프트 +- `src/types/studio.ts` - 플로우 타입, `src/types/writing.ts` - keywords?/studioResult? + +--- + ## 참고 문서 - [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 프로젝트 구조