12 KiB
12 KiB
AGENTS.md
AI 에이전트(Claude Code 등)가 이 프로젝트에서 작업할 때 따라야 할 규칙과 맥락을 정의합니다.
프로젝트 개요
중학교 2학년 대상 수학 실수 유형 반복 연습 교육용 게임 서비스. 문제 생성은 AI 없이 자체 알고리즘 엔진으로 처리한다.
기술 스택 요약
| 레이어 | 기술 |
|---|---|
| 프레임워크 | Next.js 14 (App Router) + TypeScript 5 |
| 스타일 | Tailwind CSS 3 |
| 상태 관리 | Zustand |
| 수식 렌더링 | KaTeX |
| 애니메이션 | Framer Motion |
| 인증 | NextAuth.js 4 |
| ORM | Prisma (MariaDB 11) |
| 캐시 | Redis 7 |
| 유효성 검증 | Zod |
| 패키지 매니저 | pnpm |
| 테스트 | Vitest (단위) / Playwright (E2E) |
디렉토리 구조 및 파일 위치 규칙
app/ # Next.js App Router 루트
(game)/ # 게임 화면 라우트 그룹
number-parentheses/ # 숫자 괄호 준비 화면
play/ # 숫자 괄호 풀이 화면
variable-parentheses/ # 미지수 괄호 준비 화면
play/ # 미지수 괄호 풀이 화면
select-mode/ # 게임 모드 선택 (미구현)
api/
auth/ # NextAuth.js 핸들러
problem/ # 문제 생성 API
record/ # 풀이 기록 저장 API
layout.tsx
page.tsx # 시작 화면 (학습 내용 선택)
components/
math/ # KaTeX 수식 컴포넌트
game/ # 게임 UI 컴포넌트
lib/
engine/ # 문제 생성 알고리즘 엔진
parenthesis-sign.ts # 숫자 괄호 엔진
variable-parenthesis.ts # 미지수 괄호 엔진
types.ts # 숫자 괄호 타입
variable-types.ts # 미지수 괄호 타입
input-device.ts # 입력 장치 감지 미디어 쿼리 상수
prisma.ts # Prisma Client 싱글턴
redis.ts # Redis Client 싱글턴
store/
gameStore.ts # 숫자 괄호 Zustand 전역 상태
variableGameStore.ts # 미지수 괄호 Zustand 전역 상태
types/
*.d.ts # 타입 선언이 없는 서드파티 모듈 보강
prisma/
schema.prisma # MariaDB 모델 정의
.gitea/workflows/deploy.yml # CI/CD 워크플로우
코드 작성 규칙
Next.js / React
- 페이지 컴포넌트는 반드시
app/디렉토리에 위치한다. - 클라이언트 컴포넌트에는
'use client'지시어를 파일 최상단에 명시한다. - 서버 컴포넌트가 기본값이므로, 클라이언트 기능(훅, 이벤트)이 필요할 때만
'use client'를 붙인다.
수식 렌더링
- 모든 수학 수식은 KaTeX로 렌더링한다. 일반 텍스트나 Unicode 수식 문자를 직접 사용하지 않는다.
- KaTeX 컴포넌트는
components/math/하위에 위치한다. - 현재 수식 컴포넌트는
katex.renderToString()을 직접 사용한다. 별도 래퍼 라이브러리를 추가하기 전에 Next.js App Router 클라이언트 번들에서 런타임 문제가 없는지 확인한다.
상태 관리
- 숫자 괄호 전역 게임 상태는
store/gameStore.ts, 미지수 괄호 전역 게임 상태는store/variableGameStore.ts의 Zustand store로 관리한다. 학습 유형별로 store를 분리한다. - 컴포넌트 내부의 로컬 상태는
useState를 사용한다. - Zustand selector 안에서 매번 새 객체·배열을 반환하는 함수(
score()등)를 호출하지 않는다. 필요한 파생값은 구독한 state를 기준으로 컴포넌트 내부에서 계산하거나 안정적인 selector로 분리한다. - store에는 세션 원본 상태와 상태 변경 action을 우선 두고, 렌더링 전용 파생값은 컴포넌트 또는 별도 순수 유틸에서 계산한다.
컴포넌트 배치
- 게임 화면에서 재사용되는 입력, 피드백, 점수 요약, 진행 단계 UI는
components/game/하위에 둔다. - 정답·오답 판정 라인처럼 피드백 화면과 결과 화면이 공유하는 표시는
components/game/하위 공통 컴포넌트로 분리한다. - 수식 렌더링 전용 컴포넌트는
components/math/하위에 둔다. - 라우트별 조립 컴포넌트(
NumberPlayClient,NumberParenthesesClient등)는 해당app/라우트 폴더에 둘 수 있다.
접근성 / 키보드 조작
- 문제 풀이 화면은 키보드만으로 진행할 수 있어야 한다.
- 정답·오답 피드백이 표시되면 다음 진행 버튼에 focus를 둔다.
- 최종 결과 화면이 표시되면 다시 시작 버튼에 focus를 둔다.
- 아이콘이나 기호만으로 상태를 표시할 때는
aria-label,sr-only텍스트 등으로 의미를 함께 제공한다.
숫자 괄호 풀이 화면 (/number-parentheses/play)
- 모든 항을 하나의 공유 input으로 입력한다.
- input 아래에는 각 항의 입력값 버튼을 버튼 그룹으로 표시한다. 현재 입력 중인 항은
aria-pressed로 표시하고, 비어 있는 항은?로 표시한다. - 첫째 항에서 Enter를 누르면 같은 input이 둘째 항 입력으로 전환된다.
- 둘째 항에서 Enter를 누르면 제출 동작을 실행한다. 별도 제출 버튼이 없어도 form submit 흐름이 유지되어야 한다.
- 터치 기반 입력 환경에서는 input focus 시 커스텀 숫자 키패드를 표시한다. 키패드는 숫자,
+/-,.,⌫,↵버튼을 제공한다. - 마우스/트랙패드처럼 hover 가능한 정밀 포인터가 있는 환경은 물리 키보드 사용 환경으로 간주하고 커스텀 숫자 키패드를 숨긴다. 웹 표준 API로 물리 키보드 연결 여부 자체를 안정적으로 판별할 수 없으므로 해상도 기준으로 분기하지 않는다. (
lib/input-device.ts의DESKTOP_INPUT_DEVICE_QUERY사용) - 입력 중에는
-,1.같은 임시 값을 허용할 수 있지만 제출 시에는 정상적인 양의 실수 또는 음의 실수 표현만 통과시킨다. - 문제 풀이 화면 최초 진입 시 모바일 키보드를 강제로 열지 않는다. 사용자가 input을 직접 focus하거나 피드백 후 다음 문제로 넘어갈 때 input에 focus를 둔다.
- 터치 기반 입력 환경에서 input focus 시
span.katex상단이 브라우저 화면 상단에 오도록 조정한다.
미지수 괄호 풀이 화면 (/variable-parentheses/play)
- 답을 직접 입력하지 않는다. 각 항의 보기 버튼 중에서 선택한다.
- 첫째 항 선택 후 자동으로 둘째 항 보기로 focus가 이동한다.
- 두 항을 모두 선택하면
정답 확인버튼에 focus가 이동하고 버튼이 활성화된다. - 선택된 보기 버튼은
aria-pressed로 표시한다.
API / 백엔드
- API는
app/api/하위의 Route Handlers로만 구현한다. 별도의 백엔드 서버를 추가하지 않는다. - 모든 API 요청 입력값은 Zod 스키마로 검증한다.
- DB 접근은
lib/prisma.ts의 Prisma Client 싱글턴을 통해서만 한다. - Redis 접근은
lib/redis.ts의 Redis Client 싱글턴을 통해서만 한다.
문제 생성 엔진
- 문제 생성 로직은
lib/engine/하위에 위치한다. - 문제는 AI 호출 없이 알고리즘으로 생성한다. 외부 AI API를 문제 생성에 사용하지 않는다.
- 계수는 항상 정수이어야 한다.
- 문제 세션에서 중복을 방지할 때는 랜덤 id가 아니라 수학적 문제 조건을 나타내는 안정적인 key를 기준으로 한다.
- 모든 엔진은 전체 가능한 시드 목록을 모듈 로드 시 한 번 생성(
createAllProblemSeeds)하고, 세션 생성 시 셔플 후 슬라이스하는 방식으로 중복 없이 출제한다. - 숫자 괄호 엔진:
lib/engine/parenthesis-sign.ts/ 타입:lib/engine/types.ts - 미지수 괄호 엔진:
lib/engine/variable-parenthesis.ts/ 타입:lib/engine/variable-types.ts- 괄호 안 두 항 중 정확히 하나가
정수 × x항이다. - 각 항의 보기는 정답 계수의 절대값 기준 음수/양수 두 개를 셔플해 제공한다.
- 채점은 choice id 기반으로 수행한다.
- 괄호 안 두 항 중 정확히 하나가
환경변수
새로운 환경변수를 추가할 때는 .env.example에 키 이름만 추가하고 값은 비워둔다.
.env.local은 절대 Git에 커밋하지 않는다.
DATABASE_URL=mysql://user:pass@mariadb:3306/mathgame
REDIS_URL=redis://redis:6379
NEXTAUTH_SECRET=your-secret
보안 규칙
.env.local을 Git에 커밋하거나 코드에 하드코딩하지 않는다.- MariaDB, Redis 포트는 컨테이너 내부 통신 전용이다. 외부 노출 설정을 추가하지 않는다.
- SQL 쿼리는 반드시 Prisma를 통해 실행한다. Raw query 사용 시 파라미터 바인딩을 반드시 사용한다.
- 사용자 입력은 API 경계에서 Zod로 검증한다. 클라이언트 측 검증만으로 보안을 의존하지 않는다.
게임 도메인 컨텍스트
학습 유형
| 유형 | 상태 | 라우트 | 답변 방식 |
|---|---|---|---|
| 숫자 괄호 풀기 | 구현 완료 | /number-parentheses → /number-parentheses/play |
직접 입력 |
| 미지수 괄호 풀기 | 구현 완료 | /variable-parentheses → /variable-parentheses/play |
보기 선택 |
| 분수 괄호 풀기 | 미구현 | — | — |
| 실수 괄호 풀기 | 미구현 | — | — |
게임 모드
| 모드 | 설명 |
|---|---|
| 스피드 퀴즈 | 제한 시간 내 최대 문제 풀기, Redis Sorted Set 기반 실시간 점수판 (미구현) |
| 일반 풀이 | 시간 제한 없음, 틀린 유형 반복 출제 (미구현) |
화면 흐름
학습 내용 선택(/) → 준비 화면 → 문제 풀이 → 결과
- 시작 화면(
/)은 학습 내용 선택 화면이다.숫자 괄호 풀기,미지수 괄호 풀기가 활성화되어 있고 분수/실수 괄호는 disabled 상태다. - 준비 화면은 문제 수
[5] [10]버튼 그룹과 괄호 종류 버튼 그룹을 제공한다. 기본값은10이며, 현재는()만 선택 가능하다. - 준비 화면의
연습 시작은 Zustand 세션을 초기화하고 풀이 화면으로 이동한다. - 풀이 화면은 URL의
count쿼리와 Zustand의 선택 문제 수를 동기화한다. 허용 문제 수는5,10이며 유효하지 않은 값은10으로 처리한다. - 정답·오답 피드백은
계산식 = 내가 입력한(고른) 답형식으로 표시한다. 정답은○, 오답은 붉은색 답과×, 굵은 정답값을 함께 보여준다. - 최종 결과 화면은 지금까지 푼 문제를
문제 = 내가 입력한(고른) 답형식으로 나열하고, 피드백 화면과 같은 정답·오답 색상 및 기호 규칙을 사용한다.
CI/CD
.gitea/workflows/deploy.yml이 CI/CD 진입점이다. main 브랜치 push 시 자동으로 빌드·배포된다.- 패키지 설치는 반드시
pnpm을 사용한다.npm/yarn명령을 사용하지 않는다. - 빌드 확인은
pnpm build로 실행한다. standalone 배포 산출물에 필요한 정적 파일 복사까지postbuild스크립트가 처리한다. - 배포 환경: Synology NAS + Docker Compose + Nginx Proxy Manager.
작업 시 체크리스트
새 기능 또는 수정 작업 전에 다음을 확인한다.
- 클라이언트 컴포넌트에
'use client'선언 여부 - 수식이 KaTeX로 렌더링되는지 여부
- API 입력값이 Zod로 검증되는지 여부
- DB 접근이 Prisma 싱글턴을 통하는지 여부
- 환경변수가
.env.local에만 존재하는지 여부 (.env.example에 키 이름 추가) - 문제 생성 로직이
lib/engine/내에서 알고리즘으로만 처리되는지 여부