Compare commits

..

1 Commits

Author SHA1 Message Date
jisangs 1a6639f45a Initial commit 2026-05-21 09:22:09 +00:00
23 changed files with 3 additions and 4640 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
-81
View File
@@ -1,81 +0,0 @@
# ===========================
# 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/
-10
View File
@@ -1,10 +0,0 @@
# 디폴트 무시된 파일
/shelf/
/workspace.xml
# 에디터 기반 HTTP 클라이언트 요청
/httpRequests/
# 쿼리 파일을 포함한 무시된 디폴트 폴더
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
-157
View File
@@ -1,157 +0,0 @@
# 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를 문제 생성에 사용하지 않는다.
- 미지수의 계수는 항상 정수이어야 한다.
- 생성 파라미터: 숫자 자릿수, 괄호 깊이(`()` / `(){}` / `(){}[]`), 괄호 개수(13), 미지수(없음/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/` 내에서 알고리즘으로만 처리되는지 여부
-1
View File
@@ -1 +0,0 @@
@AGENTS.md
+3
View File
@@ -0,0 +1,3 @@
# math-quiz
자주 틀리는 수학 문제를 반복 풀이
-30
View File
@@ -1,30 +0,0 @@
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>
);
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.
-27
View File
@@ -1,27 +0,0 @@
@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;
}
}
-35
View File
@@ -1,35 +0,0 @@
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>
);
}
-36
View File
@@ -1,36 +0,0 @@
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>
);
}
-153
View File
@@ -1,153 +0,0 @@
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) 포함 문제 (파라미터 확장 이터레이션)
-103
View File
@@ -1,103 +0,0 @@
# 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는 건드리지 마.
이런 식으로 “이번 단계에서 하지 않을 것”을 함께 말하면 범위가 잘 고정된다.
현재 프로젝트는 아직 파일이 거의 없는 초기 상태이므로, 위 방식으로 진행하면 작은 단위로 확인하면서 안정적으로 기능을 쌓아갈 수 있다.
-114
View File
@@ -1,114 +0,0 @@
# 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 함수와 단위 테스트를 먼저 추가한다.
-69
View File
@@ -1,69 +0,0 @@
# 바이브 코딩 진행 문서
이 폴더는 단계별 바이브 코딩을 진행하면서 생기는 계획, 검토, 회고 문서를 모아두는 곳이다.
루트 디렉토리에는 `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. 단계 제목
## 목표
이번 단계의 목표를 한 문장으로 적는다.
## 이번 단계에서 할 것
- 작업 항목
## 이번 단계에서 하지 않을 것
- 범위에서 제외할 항목
## 완료 기준
- 직접 확인 가능한 기준
## 작업 결과
작업 후 기록한다.
## 다음 단계
다음에 이어서 할 일을 적는다.
```
-4
View File
@@ -1,4 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
-26
View File
@@ -1,26 +0,0 @@
{
"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"
}
}
-3738
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
-19
View File
@@ -1,19 +0,0 @@
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;
-26
View File
@@ -1,26 +0,0 @@
{
"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"]
}