Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 582c03a985 | |||
| 1039b7b03c | |||
| 3d403111ba | |||
| 0fb900c857 | |||
| 0f51d9762a |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
# ===========================
|
||||
# macOS
|
||||
# ===========================
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# ===========================
|
||||
# IntelliJ IDEA
|
||||
# ===========================
|
||||
.idea/
|
||||
|
||||
# 모듈·프로젝트 파일 (구형 포맷)
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# ===========================
|
||||
# Next.js
|
||||
# ===========================
|
||||
.next/
|
||||
out/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# ===========================
|
||||
# Node.js / pnpm
|
||||
# ===========================
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
.pnpm-debug.log*
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# ===========================
|
||||
# 환경변수 (보안)
|
||||
# ===========================
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
# .env.example 은 커밋 대상 (키 이름만 포함)
|
||||
|
||||
# ===========================
|
||||
# Prisma
|
||||
# ===========================
|
||||
prisma/migrations/dev/
|
||||
|
||||
# ===========================
|
||||
# Vitest / Playwright
|
||||
# ===========================
|
||||
coverage/
|
||||
playwright-report/
|
||||
test-results/
|
||||
.playwright/
|
||||
|
||||
# ===========================
|
||||
# Docker
|
||||
# ===========================
|
||||
docker-compose.override.yml
|
||||
|
||||
# ===========================
|
||||
# TypeScript
|
||||
# ===========================
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# ===========================
|
||||
# 기타
|
||||
# ===========================
|
||||
*.log
|
||||
.turbo/
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# 디폴트 무시된 파일
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 에디터 기반 HTTP 클라이언트 요청
|
||||
/httpRequests/
|
||||
# 쿼리 파일을 포함한 무시된 디폴트 폴더
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@@ -0,0 +1,157 @@
|
||||
# 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)/ # 게임 화면 라우트 그룹
|
||||
select-type/ # 문제 유형 선택
|
||||
select-mode/ # 게임 모드 선택
|
||||
play/ # 문제 풀이
|
||||
api/
|
||||
auth/ # NextAuth.js 핸들러
|
||||
problem/ # 문제 생성 API
|
||||
record/ # 풀이 기록 저장 API
|
||||
layout.tsx
|
||||
page.tsx # 시작 화면
|
||||
components/
|
||||
math/ # KaTeX 수식 컴포넌트
|
||||
game/ # 게임 UI 컴포넌트
|
||||
lib/
|
||||
engine/ # 문제 생성 알고리즘 엔진
|
||||
prisma.ts # Prisma Client 싱글턴
|
||||
redis.ts # Redis Client 싱글턴
|
||||
store/
|
||||
gameStore.ts # Zustand 전역 게임 상태
|
||||
prisma/
|
||||
schema.prisma # MariaDB 모델 정의
|
||||
.gitea/workflows/deploy.yml # CI/CD 워크플로우
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 코드 작성 규칙
|
||||
|
||||
### Next.js / React
|
||||
|
||||
- 페이지 컴포넌트는 반드시 `app/` 디렉토리에 위치한다.
|
||||
- 클라이언트 컴포넌트에는 `'use client'` 지시어를 파일 최상단에 명시한다.
|
||||
- 서버 컴포넌트가 기본값이므로, 클라이언트 기능(훅, 이벤트)이 필요할 때만 `'use client'`를 붙인다.
|
||||
|
||||
### 수식 렌더링
|
||||
|
||||
- 모든 수학 수식은 KaTeX로 렌더링한다. 일반 텍스트나 Unicode 수식 문자를 직접 사용하지 않는다.
|
||||
- KaTeX 컴포넌트는 `components/math/` 하위에 위치한다.
|
||||
|
||||
### 상태 관리
|
||||
|
||||
- 전역 게임 상태(점수, 진행도, 선택한 모드·유형)는 `store/gameStore.ts`의 Zustand store로 관리한다.
|
||||
- 컴포넌트 내부의 로컬 상태는 `useState`를 사용한다.
|
||||
|
||||
### 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를 문제 생성에 사용하지 않는다.
|
||||
- 미지수의 계수는 항상 정수이어야 한다.
|
||||
- 생성 파라미터: 숫자 자릿수, 괄호 깊이(`()` / `(){}` / `(){}[]`), 괄호 개수(1–3), 미지수(없음/x/x,y/x,y,z).
|
||||
|
||||
---
|
||||
|
||||
## 환경변수
|
||||
|
||||
새로운 환경변수를 추가할 때는 `.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로 검증한다. 클라이언트 측 검증만으로 보안을 의존하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 게임 도메인 컨텍스트
|
||||
|
||||
### 문제 유형
|
||||
|
||||
1. **괄호 음수 분배**: 괄호 앞 음수 곱셈 시 괄호 내 부호 변경 (예: `-2 × (3 - 6)`)
|
||||
2. **이항 부호 변경**: 등식의 항을 이항할 때 부호 반전 (예: `3x - 5y + 8 = -2x + 2y - 2`)
|
||||
|
||||
### 게임 모드
|
||||
|
||||
| 모드 | 설명 |
|
||||
|---|---|
|
||||
| 스피드 퀴즈 | 제한 시간 내 최대 문제 풀기, Redis Sorted Set 기반 실시간 점수판 |
|
||||
| 일반 풀이 | 시간 제한 없음, 틀린 유형 반복 출제 |
|
||||
|
||||
### 화면 흐름
|
||||
|
||||
```
|
||||
시작 화면 → 문제 유형 선택 → 게임 모드 선택 → 문제 풀이 → 결과 및 기록
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD
|
||||
|
||||
- `.gitea/workflows/deploy.yml`이 CI/CD 진입점이다. main 브랜치 push 시 자동으로 빌드·배포된다.
|
||||
- 패키지 설치는 반드시 `pnpm`을 사용한다. `npm` / `yarn` 명령을 사용하지 않는다.
|
||||
- 배포 환경: Synology NAS + Docker Compose + Nginx Proxy Manager.
|
||||
|
||||
---
|
||||
|
||||
## 작업 시 체크리스트
|
||||
|
||||
새 기능 또는 수정 작업 전에 다음을 확인한다.
|
||||
|
||||
- [ ] 클라이언트 컴포넌트에 `'use client'` 선언 여부
|
||||
- [ ] 수식이 KaTeX로 렌더링되는지 여부
|
||||
- [ ] API 입력값이 Zod로 검증되는지 여부
|
||||
- [ ] DB 접근이 Prisma 싱글턴을 통하는지 여부
|
||||
- [ ] 환경변수가 `.env.local`에만 존재하는지 여부 (`.env.example`에 키 이름 추가)
|
||||
- [ ] 문제 생성 로직이 `lib/engine/` 내에서 알고리즘으로만 처리되는지 여부
|
||||
@@ -0,0 +1,30 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function PlayPage() {
|
||||
return (
|
||||
<main className="flex min-h-screen items-center bg-white px-6 py-12 text-slate-950">
|
||||
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm font-semibold text-emerald-700">
|
||||
괄호 음수 분배
|
||||
</p>
|
||||
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
||||
문제 풀이
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg leading-8 text-slate-700">
|
||||
다음 단계에서 괄호 앞 음수 분배 문제가 이 화면에 표시됩니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex h-11 items-center justify-center rounded-md border border-slate-300 px-5 text-base font-semibold text-slate-800 transition hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
|
||||
>
|
||||
처음으로
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Metadata } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/GeistVF.woff",
|
||||
variable: "--font-geist-sans",
|
||||
weight: "100 900",
|
||||
});
|
||||
const geistMono = localFont({
|
||||
src: "./fonts/GeistMonoVF.woff",
|
||||
variable: "--font-geist-mono",
|
||||
weight: "100 900",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "괄호 음수 분배 연습",
|
||||
description: "중학교 2학년 수학 실수 유형 반복 연습",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="ko">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
||||
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm font-semibold text-emerald-700">
|
||||
중학교 2학년 수학 실수 유형 연습
|
||||
</p>
|
||||
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
||||
괄호 음수 분배 연습
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg leading-8 text-slate-700">
|
||||
괄호 앞의 음수를 분배할 때 부호가 어떻게 바뀌는지 차근차근
|
||||
연습합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link
|
||||
href="/play"
|
||||
className="inline-flex h-12 items-center justify-center rounded-md bg-emerald-700 px-6 text-base font-semibold text-white transition hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
|
||||
>
|
||||
연습 시작
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="border-l-4 border-emerald-700 pl-4 text-sm leading-6 text-slate-600">
|
||||
<p>이번 단계에서는 화면 이동만 확인합니다.</p>
|
||||
<p>문제 생성과 채점은 다음 이터레이션에서 추가합니다.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
Here is Claude's plan:
|
||||
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
|
||||
이터레이션 1: 괄호 음수 분배 문제 풀이 기능
|
||||
|
||||
Context
|
||||
|
||||
완전 초기 상태(코드 없음)에서 바이브 코딩 방식으로 시작. DB·인증·Redis 없이 프론트엔드와 문제 생성
|
||||
엔진만으로 첫 기능을 동작 가능한 상태까지 만든다.
|
||||
|
||||
---
|
||||
단계별 접근법 (바이브 코딩)
|
||||
|
||||
각 단계가 독립적으로 실행·확인 가능한 단위로 나뉜다. 다음 단계로 넘어가기 전에 해당 단계가 정상
|
||||
동작하는지 함께 확인한다.
|
||||
|
||||
┌──────┬──────────────────────────────┬────────────────────────────────────┐
|
||||
│ 단계 │ 내용 │ 완료 기준 │
|
||||
├──────┼──────────────────────────────┼────────────────────────────────────┤
|
||||
│ 1 │ 프로젝트 초기화 │ pnpm dev 실행 시 Next.js 기본 화면 │
|
||||
├──────┼──────────────────────────────┼────────────────────────────────────┤
|
||||
│ 2 │ 문제 생성 엔진 + 단위 테스트 │ pnpm test 통과 │
|
||||
├──────┼──────────────────────────────┼────────────────────────────────────┤
|
||||
│ 3 │ KaTeX 수식 렌더링 컴포넌트 │ 브라우저에서 수식 정상 표시 │
|
||||
├──────┼──────────────────────────────┼────────────────────────────────────┤
|
||||
│ 4 │ Zustand 게임 상태 │ store에서 문제 생성·채점 동작 │
|
||||
├──────┼──────────────────────────────┼────────────────────────────────────┤
|
||||
│ 5 │ 게임 UI 조립 │ 문제 풀이 전체 흐름 플레이 가능 │
|
||||
└──────┴──────────────────────────────┴────────────────────────────────────┘
|
||||
|
||||
---
|
||||
1단계: 프로젝트 초기화
|
||||
|
||||
pnpm create next-app@14 . --typescript --tailwind --eslint --app --no-src-dir --import-alias "@/*"
|
||||
pnpm add zustand katex react-katex framer-motion clsx tailwind-merge
|
||||
pnpm add -D @types/katex vitest @vitejs/plugin-react jsdom @testing-library/react
|
||||
|
||||
app/layout.tsx에 추가:
|
||||
import 'katex/dist/katex.min.css'
|
||||
|
||||
---
|
||||
2단계: 문제 생성 엔진
|
||||
|
||||
lib/engine/types.ts
|
||||
|
||||
interface ParenthesisSignProblem {
|
||||
id: string
|
||||
multiplier: number // 항상 음수 (-1 ~ -9)
|
||||
a: number // 괄호 안 첫째 항 (1 ~ 9)
|
||||
operator: '+' | '-'
|
||||
b: number // 괄호 안 둘째 항 (1 ~ 9)
|
||||
distributedA: number // multiplier * a
|
||||
distributedB: number // multiplier * (op==='-' ? -b : b)
|
||||
latexBefore: string // 문제 수식
|
||||
latexAfter: string // 정답 수식
|
||||
}
|
||||
interface UserAnswer { a: number; b: number }
|
||||
interface GradeResult { correct: boolean; correctAnswer: [number, number] }
|
||||
|
||||
lib/engine/parenthesis-sign.ts
|
||||
|
||||
핵심 로직:
|
||||
- multiplier: -9 ~ -1 (음수 강제)
|
||||
- a, b: 1 ~ 9, a ≠ b
|
||||
- distributedB 계산: operator === '-' 이면 multiplier * (-b), 아니면 multiplier * b
|
||||
- KaTeX 빌드: -2(3 - 6) → 정답: -6 + 12
|
||||
|
||||
함수 목록:
|
||||
- generateProblem(): ParenthesisSignProblem
|
||||
- generateSession(count: number): ParenthesisSignProblem[] (중복 방지)
|
||||
- gradeAnswer(problem, answer): GradeResult
|
||||
|
||||
테스트 파일: lib/engine/parenthesis-sign.test.ts
|
||||
|
||||
---
|
||||
3단계: 수식 렌더링 컴포넌트
|
||||
|
||||
components/math/KatexRenderer.tsx ('use client')
|
||||
|
||||
- props: latex: string, displayMode?: boolean
|
||||
- react-katex의 InlineMath / BlockMath 래핑
|
||||
|
||||
components/math/ExpressionDisplay.tsx
|
||||
|
||||
- props: problem, showAnswer?: boolean
|
||||
- showAnswer=false: latexBefore 표시
|
||||
- showAnswer=true: latexBefore → latexAfter (Framer Motion fade-in)
|
||||
|
||||
---
|
||||
4단계: Zustand Store
|
||||
|
||||
store/gameStore.ts
|
||||
|
||||
interface GameState {
|
||||
session: { problems: ParenthesisSignProblem[]; currentIndex: number; results: (GradeResult|null)[] }
|
||||
| null
|
||||
initSession: (count?: number) => void // 기본 10문제
|
||||
submitAnswer: (answer: UserAnswer) => void
|
||||
nextProblem: () => void
|
||||
resetSession: () => void
|
||||
// 셀렉터
|
||||
currentProblem: () => ParenthesisSignProblem | null
|
||||
currentResult: () => GradeResult | null
|
||||
isComplete: () => boolean
|
||||
score: () => { correct: number; total: number }
|
||||
}
|
||||
|
||||
---
|
||||
5단계: 게임 UI 조립
|
||||
|
||||
게임 컴포넌트
|
||||
|
||||
- components/game/AnswerInput.tsx: 두 개의 숫자 입력 (distributedA, distributedB), Enter 제출 지원
|
||||
- components/game/FeedbackBanner.tsx: 정답(초록)/오답(빨강) + Framer Motion 슬라이드 인
|
||||
- components/game/ProgressBar.tsx: n/total 진행 표시
|
||||
- components/game/ScoreSummary.tsx: 최종 점수 + 다시 시작 버튼
|
||||
|
||||
페이지
|
||||
|
||||
- app/(game)/play/page.tsx: Server Component 래퍼
|
||||
- app/(game)/play/PlayClient.tsx ('use client'): 상태에 따라 분기 렌더링
|
||||
session===null → initSession()
|
||||
!isComplete() → 문제 풀이 UI
|
||||
isComplete() → ScoreSummary
|
||||
- app/page.tsx: "연습 시작" → /play 이동
|
||||
|
||||
---
|
||||
화면 흐름
|
||||
|
||||
app/page.tsx (시작)
|
||||
→ (game)/play/PlayClient.tsx
|
||||
├─ ProgressBar
|
||||
├─ ExpressionDisplay (문제 수식)
|
||||
├─ AnswerInput (두 항 입력)
|
||||
├─ FeedbackBanner (제출 후)
|
||||
├─ "다음 문제" 버튼
|
||||
└─ [완료] ScoreSummary → "다시 시작"
|
||||
|
||||
---
|
||||
검증 방법
|
||||
|
||||
1. pnpm test → 엔진 단위 테스트 전체 통과
|
||||
2. pnpm dev → 브라우저에서 수식 렌더링 확인
|
||||
3. 10문제 전체 풀이 → 점수 화면 확인
|
||||
4. 오답 입력 → 오답 피드백 및 정답 표시 확인
|
||||
5. "다시 시작" → 새 세션 생성 확인
|
||||
|
||||
---
|
||||
이번 이터레이션에서 제외
|
||||
|
||||
- DB(MariaDB), Redis, NextAuth (2차 이터레이션)
|
||||
- 유형 선택 / 모드 선택 화면 (3차 이터레이션)
|
||||
- 스피드 퀴즈 모드 (4차 이터레이션)
|
||||
- 미지수(x, y, z) 포함 문제 (파라미터 확장 이터레이션)
|
||||
@@ -0,0 +1,103 @@
|
||||
# dev-step-01 계획 검토 및 단계별 진행 제안
|
||||
|
||||
`dev-step-01.md`의 방향은 전반적으로 좋다. 특히 첫 이터레이션에서 DB, 인증, Redis, 유형 선택, 모드 선택을 제외하고 **괄호 앞 음수 분배 문제 풀이 기능만 먼저 만든다**는 판단은 `AGENTS.md`의 프로젝트 방향과 잘 맞는다.
|
||||
|
||||
다만 첫 단계는 조금 더 작게 쪼개는 것이 좋다. 현재 계획의 “1단계: 프로젝트 초기화”는 적절하지만, 바로 Zustand, Framer Motion, react-katex까지 모두 설치하면 첫 확인 지점이 흐려질 수 있다.
|
||||
|
||||
## 추천 첫 단계
|
||||
|
||||
1. Next.js 프로젝트 초기화
|
||||
2. Tailwind 기본 화면 정리
|
||||
3. `/`에서 “괄호 음수 분배 연습 시작” 버튼만 보이게 만들기
|
||||
4. `/play` 빈 페이지까지 라우팅 확인
|
||||
5. `pnpm dev`로 브라우저에서 화면 이동 확인
|
||||
|
||||
첫 단계의 완료 기준은 “앱이 실행되고 시작 화면에서 풀이 화면으로 이동된다” 정도가 적당하다. 아직 문제 생성, KaTeX, 상태 관리는 넣지 않는다.
|
||||
|
||||
## 단계별 진행안
|
||||
|
||||
### 1차 이터레이션: 화면 뼈대
|
||||
|
||||
- `app/page.tsx`
|
||||
- `app/(game)/play/page.tsx`
|
||||
- 시작 버튼에서 `/play`로 이동
|
||||
- 아직 문제 생성 없음
|
||||
- 완료 기준: 브라우저에서 시작 화면에서 풀이 화면으로 이동 확인
|
||||
|
||||
### 2차 이터레이션: 문제 생성 엔진
|
||||
|
||||
- `lib/engine/parenthesis-sign.ts`
|
||||
- `generateProblem`
|
||||
- `gradeAnswer`
|
||||
- Vitest 테스트
|
||||
- UI는 건드리지 않고 순수 알고리즘만 검증
|
||||
- 완료 기준: `pnpm test` 통과
|
||||
|
||||
### 3차 이터레이션: 수식 표시
|
||||
|
||||
- `components/math/KatexRenderer.tsx`
|
||||
- KaTeX CSS import
|
||||
- `/play`에 임시 문제 하나 표시
|
||||
- 완료 기준: 브라우저에서 `-2(3 - 6)` 같은 수식이 KaTeX로 표시됨
|
||||
|
||||
### 4차 이터레이션: 한 문제 풀기
|
||||
|
||||
- 답 입력 UI
|
||||
- 제출 버튼
|
||||
- 채점 결과 표시
|
||||
- 아직 Zustand 없이 `useState`만 사용해도 충분함
|
||||
- 완료 기준: 한 문제를 풀고 정답/오답 피드백 확인
|
||||
|
||||
### 5차 이터레이션: 10문제 세션
|
||||
|
||||
- 이때 Zustand 도입
|
||||
- `initSession`
|
||||
- `submitAnswer`
|
||||
- `nextProblem`
|
||||
- `score`
|
||||
- 완료 기준: 10문제 풀이 후 점수 화면 표시
|
||||
|
||||
### 6차 이터레이션: UX 다듬기
|
||||
|
||||
- ProgressBar
|
||||
- FeedbackBanner
|
||||
- ScoreSummary
|
||||
- Framer Motion은 이 단계에서 도입해도 충분함
|
||||
|
||||
## Claude 계획에서 조정하면 좋은 점
|
||||
|
||||
Claude의 계획에서 가장 조정하면 좋은 부분은 **Zustand와 Framer Motion을 너무 빨리 넣지 않는 것**이다.
|
||||
|
||||
첫 기능의 핵심은 다음 세 가지다.
|
||||
|
||||
1. 문제를 정확히 만든다.
|
||||
2. 문제를 화면에 보여준다.
|
||||
3. 사용자의 답을 채점한다.
|
||||
|
||||
상태 관리와 애니메이션은 이 기본 동작이 확인된 뒤 붙이는 편이 단계별 바이브 코딩에 더 잘 맞는다.
|
||||
|
||||
## 단계별 바이브 코딩 진행 방식
|
||||
|
||||
매 단계마다 아래 순서를 따른다.
|
||||
|
||||
```text
|
||||
1. 이번 단계 목표를 한 문장으로 정한다
|
||||
2. 수정할 파일을 2~4개 이하로 제한한다
|
||||
3. 완료 기준을 명확히 둔다
|
||||
4. 실행해서 직접 확인한다
|
||||
5. 다음 단계로 넘어가기 전에 짧게 회고한다
|
||||
```
|
||||
|
||||
## 다음 요청 예시
|
||||
|
||||
첫 개발 요청은 이렇게 주면 좋다.
|
||||
|
||||
> 1차 이터레이션으로 Next.js 14 프로젝트를 초기화하고, 시작 화면과 `/play` 페이지 이동까지만 구현해줘. AGENTS.md 규칙을 따르고, 아직 문제 생성 엔진이나 DB/Auth/Redis는 만들지 마.
|
||||
|
||||
그 다음 요청은 이렇게 이어갈 수 있다.
|
||||
|
||||
> 2차 이터레이션으로 `lib/engine/`에 괄호 앞 음수 분배 문제 생성 엔진과 Vitest 단위 테스트를 추가해줘. UI는 건드리지 마.
|
||||
|
||||
이런 식으로 “이번 단계에서 하지 않을 것”을 함께 말하면 범위가 잘 고정된다.
|
||||
|
||||
현재 프로젝트는 아직 파일이 거의 없는 초기 상태이므로, 위 방식으로 진행하면 작은 단위로 확인하면서 안정적으로 기능을 쌓아갈 수 있다.
|
||||
@@ -0,0 +1,114 @@
|
||||
# 01. 프로젝트 시작 화면과 라우팅
|
||||
|
||||
## 목표
|
||||
|
||||
Next.js 14 프로젝트를 초기화하고, 시작 화면에서 문제 풀이 화면(`/play`)으로 이동할 수 있는 가장 작은 앱 흐름을 만든다.
|
||||
|
||||
## 이번 단계에서 할 것
|
||||
|
||||
- Next.js 14 App Router 기반 프로젝트를 초기화한다.
|
||||
- TypeScript, Tailwind CSS, ESLint를 함께 설정한다.
|
||||
- 루트 시작 화면(`/`)을 만든다.
|
||||
- 문제 풀이 화면(`/play`)을 만든다.
|
||||
- 시작 화면의 버튼 또는 링크를 통해 `/play`로 이동할 수 있게 한다.
|
||||
- `pnpm dev`로 앱 실행을 확인한다.
|
||||
|
||||
## 이번 단계에서 하지 않을 것
|
||||
|
||||
- 문제 생성 엔진 구현
|
||||
- KaTeX 수식 렌더링
|
||||
- 답 입력 및 채점 UI
|
||||
- Zustand 상태 관리
|
||||
- Framer Motion 애니메이션
|
||||
- DB, Prisma, Redis, NextAuth 설정
|
||||
- 유형 선택 화면
|
||||
- 모드 선택 화면
|
||||
- 테스트 환경 구성
|
||||
|
||||
## 수정 또는 생성 예상 파일
|
||||
|
||||
프로젝트 초기화 후 다음 파일들이 핵심 확인 대상이 된다.
|
||||
|
||||
- `app/layout.tsx`
|
||||
- `app/page.tsx`
|
||||
- `app/(game)/play/page.tsx`
|
||||
- `app/globals.css`
|
||||
- `package.json`
|
||||
- `tailwind.config.ts`
|
||||
- `tsconfig.json`
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. `pnpm create next-app@14 . --typescript --tailwind --eslint --app --no-src-dir --import-alias "@/*"`로 프로젝트를 초기화한다.
|
||||
2. 생성된 기본 파일 구조를 확인한다.
|
||||
3. `app/page.tsx`를 시작 화면으로 정리한다.
|
||||
4. `app/(game)/play/page.tsx`를 추가해 빈 풀이 화면을 만든다.
|
||||
5. 시작 화면에서 `/play`로 이동하는 링크를 추가한다.
|
||||
6. `pnpm dev`를 실행해 브라우저에서 화면 이동을 확인한다.
|
||||
|
||||
## 화면 흐름
|
||||
|
||||
```text
|
||||
/ 시작 화면
|
||||
- 괄호 음수 분배 연습 시작
|
||||
- 시작 버튼 클릭
|
||||
|
||||
↓
|
||||
|
||||
/play 풀이 화면
|
||||
- 아직 실제 문제는 표시하지 않음
|
||||
- 다음 단계에서 문제 생성 엔진과 연결 예정
|
||||
```
|
||||
|
||||
## 시작 화면 초안
|
||||
|
||||
시작 화면은 기능 소개보다 바로 연습을 시작할 수 있는 간단한 화면으로 만든다.
|
||||
|
||||
- 제목: 괄호 음수 분배 연습
|
||||
- 보조 문구: 괄호 앞의 음수를 분배할 때 부호가 어떻게 바뀌는지 연습한다.
|
||||
- 주요 액션: 연습 시작
|
||||
|
||||
## 풀이 화면 초안
|
||||
|
||||
`/play` 화면은 아직 실제 문제 풀이 기능을 넣지 않는다.
|
||||
|
||||
- 제목: 문제 풀이
|
||||
- 보조 문구: 다음 단계에서 괄호 음수 분배 문제가 표시된다.
|
||||
- 돌아가기 링크: 처음으로
|
||||
|
||||
## 완료 기준
|
||||
|
||||
- `pnpm dev`가 정상 실행된다.
|
||||
- 브라우저에서 `/` 페이지가 열린다.
|
||||
- 시작 화면에서 “연습 시작”을 누르면 `/play`로 이동한다.
|
||||
- `/play`에서 “처음으로”를 누르면 `/`로 돌아온다.
|
||||
- 콘솔에 명백한 런타임 오류가 없다.
|
||||
|
||||
## 확인 명령
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
필요하면 초기화 직후 다음 명령으로 정적 검사를 확인한다.
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
## 작업 결과
|
||||
|
||||
- Next.js 14 App Router 프로젝트를 초기화했다.
|
||||
- TypeScript, Tailwind CSS, ESLint 설정을 추가했다.
|
||||
- `/` 시작 화면을 만들었다.
|
||||
- `/play` 풀이 화면을 만들었다.
|
||||
- 시작 화면의 “연습 시작” 링크로 `/play` 이동을 확인했다.
|
||||
- `/play` 화면의 “처음으로” 링크로 `/` 복귀를 확인했다.
|
||||
- `pnpm lint` 통과를 확인했다.
|
||||
- `pnpm dev` 실행 후 브라우저에서 화면 이동을 확인했다.
|
||||
|
||||
## 다음 단계
|
||||
|
||||
`02-parenthesis-sign-engine.md`에서 괄호 앞 음수 분배 문제 생성 엔진을 만든다.
|
||||
|
||||
다음 단계에서는 UI를 확장하지 않고, `lib/engine/` 아래에 순수 TypeScript 함수와 단위 테스트를 먼저 추가한다.
|
||||
@@ -0,0 +1,69 @@
|
||||
# 바이브 코딩 진행 문서
|
||||
|
||||
이 폴더는 단계별 바이브 코딩을 진행하면서 생기는 계획, 검토, 회고 문서를 모아두는 곳이다.
|
||||
|
||||
루트 디렉토리에는 `AGENTS.md`, `README.md`, 설정 파일처럼 프로젝트의 공식 입구 역할을 하는 파일만 두고, 작업 과정에서 생기는 문서는 이 폴더 아래에서 관리한다.
|
||||
|
||||
## 현재 목표
|
||||
|
||||
괄호 앞 음수 분배 문제를 웹 화면에서 풀 수 있는 첫 기능을 만든다.
|
||||
|
||||
## 진행 단계
|
||||
|
||||
- [x] [01. 프로젝트 시작 화면과 라우팅](./01-project-start.md)
|
||||
- [ ] 02. 괄호 앞 음수 분배 문제 생성 엔진
|
||||
- [ ] 03. KaTeX 수식 렌더링
|
||||
- [ ] 04. 한 문제 풀이 UI
|
||||
- [ ] 05. 10문제 세션과 점수
|
||||
- [ ] 06. UX 다듬기
|
||||
|
||||
## 참고 문서
|
||||
|
||||
- [Claude 초기 계획](./00-claude-initial-plan.md)
|
||||
- [초기 계획 검토](./00-plan-review.md)
|
||||
|
||||
## 문서 작성 규칙
|
||||
|
||||
새로운 단계 문서는 다음 형식으로 만든다.
|
||||
|
||||
```text
|
||||
NN-short-feature-name.md
|
||||
```
|
||||
|
||||
예시:
|
||||
|
||||
```text
|
||||
01-project-start.md
|
||||
02-parenthesis-sign-engine.md
|
||||
03-katex-rendering.md
|
||||
```
|
||||
|
||||
각 단계 문서는 아래 구조를 기본으로 사용한다.
|
||||
|
||||
```md
|
||||
# NN. 단계 제목
|
||||
|
||||
## 목표
|
||||
|
||||
이번 단계의 목표를 한 문장으로 적는다.
|
||||
|
||||
## 이번 단계에서 할 것
|
||||
|
||||
- 작업 항목
|
||||
|
||||
## 이번 단계에서 하지 않을 것
|
||||
|
||||
- 범위에서 제외할 항목
|
||||
|
||||
## 완료 기준
|
||||
|
||||
- 직접 확인 가능한 기준
|
||||
|
||||
## 작업 결과
|
||||
|
||||
작업 후 기록한다.
|
||||
|
||||
## 다음 단계
|
||||
|
||||
다음에 이어서 할 일을 적는다.
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "math-quiz",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "14.2.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.35"
|
||||
}
|
||||
}
|
||||
Generated
+3738
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user