356 lines
12 KiB
Markdown
356 lines
12 KiB
Markdown
# 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 <h1>{t('title')}</h1>;
|
|
|
|
// ❌ Bad
|
|
return <h1>새 페이지</h1>; // 하드코딩 금지!
|
|
```
|
|
|
|
**상세**: `I18N_GUIDE.md` 참조
|
|
|
|
---
|
|
|
|
### 스타일 가이드
|
|
|
|
**버튼에는 항상 `colorPalette="brand"` 사용**:
|
|
|
|
```tsx
|
|
// ✅ Good
|
|
<Button colorPalette="brand">클릭</Button>
|
|
|
|
// ❌ Bad
|
|
<Button>클릭</Button> // 기본 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.
|
|
|
|
---
|
|
|
|
<!-- COMMENT_STYLE:BEGIN (자동 생성 - 직접 수정하지 말고 ~/.claude/COMMENT_STYLE.md 를 고칠 것) -->
|
|
|
|
# 코드 주석 스타일 규칙
|
|
|
|
AI가 코드에 주석을 쓸 때 지켜야 할 규칙. 이 문서가 원본이며 도구별 설정 파일은 이 문서를 참조만 한다.
|
|
|
|
## 0. 한 줄 요약
|
|
|
|
**짧게. 왜(why)는 한 구절로만. 설명이 세 문장을 넘으면 그건 주석이 아니라 문서다.**
|
|
|
|
---
|
|
|
|
## 1. 표기
|
|
|
|
- 한국어로 쓴다. 코드 식별자·prop명·기술 용어는 원문 그대로 둔다 (`flex`, `css`, `콜백`, `레이아웃`, `UI`, `undef`).
|
|
- **종결어미는 자유.** 반말 평서형(`~한다`), 명사형(`~제한`), 음슴체(`~함`, `~됨`)를 섞어 써도 된다. 짧은 쪽이 우선이고 어미는 따지지 않는다. 존댓말만 금지.
|
|
- 대시는 하이픈 `-` 를 쓴다. em dash `—` 금지.
|
|
- 주석 안에서 마크다운 볼드 `**...**` 금지.
|
|
- 홑낫표 `「」` 금지. 강조가 필요하면 큰따옴표.
|
|
- 이모지 금지.
|
|
- `// ======` 류 섹션 구분선은 새로 만들지 않는다.
|
|
|
|
## 2. 위치 — 트레일링 vs 윗줄
|
|
|
|
**대상이 한 줄이면 그 줄 끝에, 여러 줄을 감싸는 블록이면 윗줄에.**
|
|
|
|
```tsx
|
|
// GOOD - prop 하나 = 트레일링
|
|
<HStack
|
|
alignContent="flex-start" // 요소가 위에서 아래로 정렬
|
|
overflowY="auto"
|
|
css={{scrollbarGutter: "stable"}} // 스크롤 바 영역 상시 준비
|
|
>
|
|
|
|
// BAD - prop 하나인데 줄을 잡아먹음
|
|
<HStack
|
|
// 넘칠 때 줄이 위에서부터 쌓여야 한다. 가운데로 모으면 위로 넘친
|
|
// 만큼이 스크롤로 닿지 않아 첫 줄이 잘린다.
|
|
alignContent="flex-start"
|
|
>
|
|
```
|
|
|
|
```tsx
|
|
// GOOD - JSX 섹션(여러 줄)은 윗줄에
|
|
{/* 추천 단어 영역 - 없으면 섹션 자체를 숨긴다. */}
|
|
{state.openingChips.length > 0 && (
|
|
...
|
|
)}
|
|
```
|
|
|
|
- interface 필드나 JSX prop이 여러 개일 때, 성격이 다른 것끼리는 **빈 줄로 그룹을 나눈다.**
|
|
|
|
## 3. 형식 — JSDoc은 아껴 쓴다
|
|
|
|
| 대상 | 형식 |
|
|
|---|---|
|
|
| 상수, 지역 변수, 내부 헬퍼 함수 | 한 줄 `//` |
|
|
| `@param` 등 태그가 실제로 필요한 함수·콜백 | `/** */` |
|
|
| 타입 별칭(`export type`), `interface` 선언 | `/** */` 한 줄. 외부에 노출되는 선언이라 JSDoc으로 둔다 |
|
|
| 컴포넌트·모듈 최상단 | `/** */`, 요약 1줄 + 본문 1~2줄 |
|
|
| JSX 블록 | `{/* */}` |
|
|
|
|
```ts
|
|
// GOOD
|
|
// 초안 상자의 최대 높이 제한
|
|
const SENTENCE_MAX_H_REM = 8;
|
|
|
|
// BAD - 상수에 6줄 JSDoc
|
|
/**
|
|
* 빈칸 문장 상자 하나가 커질 수 있는 한계(rem).
|
|
*
|
|
* 상자는 남는 높이를 나눠 가지며 자라다가 이 값에서 멈춘다. 없으면 큰 화면에서
|
|
* 상자 하나가 화면 절반을 차지한다. ...
|
|
*/
|
|
const SENTENCE_MAX_H_REM = 8;
|
|
```
|
|
|
|
```ts
|
|
// GOOD - @param이 필요하니 JSDoc 유지, 설명은 한 줄
|
|
/**
|
|
* @param viaChip 추천 낱말을 눌러 채웠으면 true, 직접 타이핑이면 undef. (로깅용)
|
|
*/
|
|
onBlankChange: (blankId: string, value: string, viaChip?: boolean) => void;
|
|
|
|
// GOOD - 태그 없는 콜백은 한 줄 //
|
|
// 하단 선물 나중에 받기 버튼 콜백
|
|
onRewardTiming: (timing: "now" | "later") => void;
|
|
```
|
|
|
|
컴포넌트 최상단 JSDoc:
|
|
|
|
```tsx
|
|
// GOOD
|
|
/**
|
|
* 이야기 시작 - 초안을 받아서 빈칸 채우는 단계
|
|
*
|
|
* 초안을 기반으로 추천 단어를 활용해서 빈칸을 채운다.
|
|
*/
|
|
```
|
|
- `(폴더명)` 같은 접두사를 붙이지 않는다.
|
|
- 화면/모듈이 무엇이고 무슨 일을 하는지까지. 그 이상은 쓰지 않는다.
|
|
|
|
## 4. 내용 — 무엇을 쓰고 무엇을 쓰지 않는가
|
|
|
|
### 쓴다
|
|
- **왜 이렇게 했는지를 한 구절로.** 안 그러면 뭐가 깨지는지 짧게 붙여도 좋다.
|
|
```tsx
|
|
maxH={{md: `${SENTENCE_MAX_H_REM}rem`}} // 단 SENTENCE_MAX_H_REM 까지만 늘어난다 - 그러면 UI가 한쪽으로 몰리게 됨
|
|
```
|
|
```tsx
|
|
{/* 빈칸 상자들 - 남은 공간을 동일한 높이로 나눠갖는다. 단 너무 크면 레이아웃이 퍼지니까 최대 높이를 주어서 제한 */}
|
|
```
|
|
- **블록의 이름표.** 그 자리가 무슨 영역인지 (`추천 단어 영역`, `보상 나중에 받기 미선택 css`).
|
|
- **동작 분기·숨김 조건.** (`없으면 섹션 자체를 숨긴다`)
|
|
- 코드만 봐서는 못 읽는 부수 효과, 순서 의존.
|
|
- **함정 경고의 실측 수치는 남긴다.** "다시 밟으면 큰일 난다"는 경고에 한해, 근거 수치 하나를 괄호로 짧게. 여러 개 나열하지 않는다.
|
|
```ts
|
|
// blob: URL 우선 - data: URL은 콘솔에 그대로 찍혀 메인 스레드가 멈춘다(실측 10.9초)
|
|
```
|
|
성능 함정이 아닌 곳(레이아웃 값 조정 근거 등)의 수치는 §4 "매직넘버 근거 계산"대로 자른다.
|
|
|
|
### 쓰지 않는다
|
|
| 금지 | 예시 (전부 삭제 대상) |
|
|
|---|---|
|
|
| 과거 이력 회고 | `상한이 없던 시절에는 상자 하나가 화면 절반이 됐다` |
|
|
| 하지 않은 선택 서술 | `왼쪽 글감 레일은 여기부터 두지 않는다. 1·2단계에서 이미 알려 줬고…` |
|
|
| 다른 파일 교차 참조 | `(StudioConfirmBar와 같은 규칙 - 브랜드 톤 위에 놓인 …)` |
|
|
| 매직넘버 근거 계산 | `한 줄 문장 + 역할 안내가 5rem 안팎이므로, 그보다 넉넉하되…` |
|
|
| CSS/언어 원리 강의 | `minH={0}은 반대 방향의 허락이다. 없으면 각 칸이 제 내용보다…` |
|
|
| 자명한 의도 반복 | `시안의 * 두 줄 - 줄바꿈을 그대로 살린다` |
|
|
| 코드를 그대로 읽어주기 | `// 상태 초기값 설정` / `// 성공 응답` |
|
|
| 문단 나눈 장문 서술 | JSDoc 안에 빈 줄로 나뉜 3~4문단 |
|
|
|
|
## 5. 분량 기준
|
|
|
|
- **트레일링 주석(줄 끝): 1줄.** 한 줄에 안 들어가면 내용을 잘라낸다. 가로로 늘리지 않는다.
|
|
- **윗줄 주석(`//`, `{/* */}`): 2줄까지.** 3줄이 필요하면 내용을 잘라낸다.
|
|
- 컴포넌트 JSDoc: **요약 1줄 + 본문 1~2줄.**
|
|
- 주석이 코드보다 눈에 먼저 들어오면 과한 것이다. 한 파일에서 주석이 전체의 10%를 넘어가면 줄일 곳을 찾는다.
|
|
|
|
## 6. 자기 점검
|
|
|
|
주석을 쓰기 전에:
|
|
1. 이 코드를 지우고 주석만 읽어도 되는 내용인가? → 그건 문서에 쓸 내용이지 주석이 아니다.
|
|
2. 코드가 이미 말하고 있는가? → 지운다.
|
|
3. 세 문장 이상 필요한가? → 두 문장으로 못 줄이면, 설명이 필요한 건 주석이 아니라 코드 구조다.
|
|
|
|
<!-- COMMENT_STYLE:END -->
|
|
|
|
|
|
|