Initial Commit
This commit is contained in:
594
HANZI-LEARNING-APP-SPECIFICATION.md
Normal file
594
HANZI-LEARNING-APP-SPECIFICATION.md
Normal file
@@ -0,0 +1,594 @@
|
||||
# MemoHanzi - Implementation Specification
|
||||
|
||||
**Version:** 1.0
|
||||
**Status:** Ready for Implementation
|
||||
**Target:** Claude Code
|
||||
**Application Name:** MemoHanzi (记汉字 - "Remember Hanzi")
|
||||
|
||||
---
|
||||
|
||||
## Quick Start Summary
|
||||
|
||||
**What:** MemoHanzi is a self-hosted web app for learning Chinese characters (hanzi) using spaced repetition (SM-2 algorithm)
|
||||
|
||||
**Tech Stack:**
|
||||
- Next.js 16 (TypeScript, App Router, Server Actions)
|
||||
- PostgreSQL 18 + Prisma ORM
|
||||
- NextAuth.js v5 for authentication
|
||||
- Docker Compose deployment with Nginx reverse proxy
|
||||
- Tailwind CSS, React Hook Form, Zod validation, Recharts
|
||||
|
||||
**MVP Timeline:** 10-12 weeks
|
||||
|
||||
---
|
||||
|
||||
## 1. Core Features (MVP)
|
||||
|
||||
### User Features
|
||||
- ✅ Registration/Login with email & password
|
||||
- ✅ Create and manage personal hanzi collections
|
||||
- ✅ Browse and use global HSK-level collections
|
||||
- ✅ Learning sessions with 4-choice pinyin quiz
|
||||
- ✅ SM-2 spaced repetition algorithm
|
||||
- ✅ Progress tracking & statistics dashboard
|
||||
- ✅ Search hanzi database (by character, pinyin, meaning)
|
||||
- ✅ User preferences (language, display options, learning settings)
|
||||
|
||||
### Admin Features
|
||||
- ✅ Import hanzi data (JSON/CSV from HSK vocabulary source)
|
||||
- ✅ Manage global collections
|
||||
- ✅ User management (roles, activation)
|
||||
|
||||
---
|
||||
|
||||
## 2. System Architecture
|
||||
|
||||
### Deployment Stack
|
||||
```
|
||||
[Nginx Reverse Proxy:80/443]
|
||||
↓ HTTPS/Rate Limiting/Caching
|
||||
[Next.js App:3000]
|
||||
↓ Prisma ORM
|
||||
[PostgreSQL:5432]
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
memohanzi/
|
||||
├── src/
|
||||
│ ├── app/ # Next.js App Router
|
||||
│ │ ├── (auth)/ # Login, register
|
||||
│ │ ├── (app)/ # Dashboard, learn, collections, hanzi, progress, settings
|
||||
│ │ └── (admin)/ # Admin pages
|
||||
│ ├── actions/ # Server Actions (auth, collections, hanzi, learning, etc.)
|
||||
│ ├── components/ # React components
|
||||
│ ├── lib/ # Utils (SM-2 algorithm, parsers, validation)
|
||||
│ └── types/ # TypeScript types
|
||||
├── prisma/
|
||||
│ └── schema.prisma # Database schema
|
||||
├── docker/
|
||||
│ ├── Dockerfile
|
||||
│ └── nginx.conf
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Database Schema (Prisma)
|
||||
|
||||
### Core Models
|
||||
|
||||
**Language** - Stores supported translation languages
|
||||
- Fields: code (ISO 639-1), name, nativeName, isActive
|
||||
|
||||
**Hanzi** - Base hanzi information
|
||||
- Fields: simplified (unique), radical, frequency
|
||||
- Relations: forms, hskLevels, partsOfSpeech, userProgress, collectionItems
|
||||
|
||||
**HanziForm** - Traditional variants
|
||||
- Fields: hanziId, traditional, isDefault
|
||||
- Relations: transcriptions, meanings, classifiers
|
||||
|
||||
**HanziTranscription** - Multiple transcription types
|
||||
- Fields: formId, type (pinyin/numeric/wadegiles/etc), value
|
||||
|
||||
**HanziMeaning** - Multi-language meanings
|
||||
- Fields: formId, languageId, meaning, orderIndex
|
||||
|
||||
**HanziHSKLevel** - HSK level tags
|
||||
- Fields: hanziId, level (e.g., "new-1", "old-3")
|
||||
|
||||
**HanziPOS** - Parts of speech
|
||||
- Fields: hanziId, pos (n/v/adj/etc)
|
||||
|
||||
**HanziClassifier** - Measure words
|
||||
- Fields: formId, classifier
|
||||
|
||||
### User & Auth Models
|
||||
|
||||
**User**
|
||||
- Fields: email, password (hashed), name, role (USER/ADMIN/MODERATOR), isActive
|
||||
- Relations: collections, hanziProgress, preferences, sessions
|
||||
|
||||
**UserPreference**
|
||||
- Fields: preferredLanguageId, characterDisplay (SIMPLIFIED/TRADITIONAL/BOTH), transcriptionType, cardsPerSession, dailyGoal, removalThreshold, allowManualDifficulty
|
||||
|
||||
**Account, Session, VerificationToken** - NextAuth.js standard models
|
||||
|
||||
### Learning Models
|
||||
|
||||
**Collection**
|
||||
- Fields: name, description, isGlobal, createdBy, isPublic
|
||||
- Relations: items (CollectionItem join table)
|
||||
|
||||
**CollectionItem** - Join table
|
||||
- Fields: collectionId, hanziId, orderIndex
|
||||
|
||||
**UserHanziProgress** - Tracks learning per hanzi
|
||||
- Fields: userId, hanziId, correctCount, incorrectCount, consecutiveCorrect
|
||||
- SM-2 fields: easeFactor (default 2.5), interval (default 1), nextReviewDate
|
||||
- Manual override: manualDifficulty (EASY/MEDIUM/HARD/SUSPENDED)
|
||||
|
||||
**LearningSession** - Track study sessions
|
||||
- Fields: userId, startedAt, endedAt, cardsReviewed, correctAnswers, incorrectAnswers, collectionId
|
||||
- Relations: reviews (SessionReview)
|
||||
|
||||
**SessionReview** - Individual card reviews
|
||||
- Fields: sessionId, hanziId, isCorrect, responseTime
|
||||
|
||||
---
|
||||
|
||||
## 4. Server Actions API
|
||||
|
||||
All actions return: `{ success: boolean, data?: T, message?: string, errors?: Record<string, string[]> }`
|
||||
|
||||
### Authentication (`src/actions/auth.ts`)
|
||||
- `register(email, password, name)` - Create account
|
||||
- `login(email, password)` - Authenticate
|
||||
- `logout()` - End session
|
||||
- `updatePassword(current, new)` - Change password
|
||||
- `updateProfile(name, email, image)` - Update user
|
||||
|
||||
### Collections (`src/actions/collections.ts`)
|
||||
- `createCollection(name, description, isPublic)` - New collection
|
||||
- `updateCollection(id, data)` - Modify (owner/admin only)
|
||||
- `deleteCollection(id)` - Remove (owner/admin only)
|
||||
- `getCollection(id)` - Get with hanzi
|
||||
- `getUserCollections()` - List user's collections
|
||||
- `getGlobalCollections()` - List HSK collections
|
||||
- `addHanziToCollection(collectionId, hanziIds[])` - Add hanzi
|
||||
- `removeHanziFromCollection(collectionId, hanziId)` - Remove hanzi
|
||||
|
||||
### Hanzi (`src/actions/hanzi.ts`)
|
||||
- `searchHanzi(query, hskLevel?, limit, offset)` - Search database (public)
|
||||
- `getHanzi(id)` - Get details (public)
|
||||
- `getHanziBySimplified(char)` - Lookup by character (public)
|
||||
|
||||
### Learning (`src/actions/learning.ts`)
|
||||
- `startLearningSession(collectionId?, cardsCount)` - Begin session, returns cards
|
||||
- `submitAnswer(sessionId, hanziId, selected, correct, time)` - Record answer, updates SM-2
|
||||
- `endSession(sessionId)` - Complete, return summary
|
||||
- `getDueCards()` - Get counts (now, today, week)
|
||||
- `updateCardDifficulty(hanziId, difficulty)` - Manual override
|
||||
- `removeFromLearning(hanziId)` - Stop learning card
|
||||
|
||||
### Progress (`src/actions/progress.ts`)
|
||||
- `getUserProgress(dateRange?)` - Overall stats & charts
|
||||
- `getHanziProgress(hanziId)` - Individual hanzi stats
|
||||
- `getLearningSessions(limit?)` - Session history
|
||||
- `getStatistics()` - Dashboard stats
|
||||
- `resetHanziProgress(hanziId)` - Reset card
|
||||
|
||||
### Preferences (`src/actions/preferences.ts`)
|
||||
- `getPreferences()` - Get settings
|
||||
- `updatePreferences(data)` - Update settings
|
||||
- `getAvailableLanguages()` - List languages
|
||||
|
||||
### Admin (`src/actions/admin.ts`)
|
||||
- `createGlobalCollection(name, description, hskLevel)` - HSK collection
|
||||
- `importHanzi(fileData, format)` - Bulk import (JSON/CSV)
|
||||
- `getImportHistory()` - Past imports
|
||||
- `getUserManagement(page, pageSize)` - List users
|
||||
- `updateUserRole(userId, role)` - Change role
|
||||
- `toggleUserStatus(userId)` - Activate/deactivate
|
||||
|
||||
---
|
||||
|
||||
## 5. SM-2 Algorithm Implementation
|
||||
|
||||
### Initial Values
|
||||
- easeFactor: 2.5
|
||||
- interval: 1 day
|
||||
- consecutiveCorrect: 0
|
||||
|
||||
### On Correct Answer
|
||||
```javascript
|
||||
if (consecutiveCorrect === 0) {
|
||||
interval = 1
|
||||
} else if (consecutiveCorrect === 1) {
|
||||
interval = 6
|
||||
} else {
|
||||
interval = Math.round(interval * easeFactor)
|
||||
}
|
||||
|
||||
easeFactor = easeFactor + 0.1 // Can adjust based on quality
|
||||
consecutiveCorrect++
|
||||
nextReviewDate = now + interval days
|
||||
```
|
||||
|
||||
### On Incorrect Answer
|
||||
```javascript
|
||||
interval = 1
|
||||
consecutiveCorrect = 0
|
||||
nextReviewDate = now + 1 day
|
||||
easeFactor = Math.max(1.3, easeFactor - 0.2)
|
||||
```
|
||||
|
||||
### Card Selection
|
||||
1. Query: `WHERE nextReviewDate <= now AND userId = currentUser`
|
||||
2. Apply manual difficulty (SUSPENDED = exclude, HARD = priority, EASY = depriority)
|
||||
3. Sort: nextReviewDate ASC, incorrectCount DESC, consecutiveCorrect ASC
|
||||
4. Limit to user's cardsPerSession
|
||||
5. If not enough, add new cards from collections
|
||||
|
||||
### Wrong Answer Generation
|
||||
- Select 3 random incorrect pinyin from same HSK level
|
||||
- Ensure no duplicates
|
||||
- Randomize order (Fisher-Yates shuffle)
|
||||
|
||||
---
|
||||
|
||||
## 6. UI/UX Pages
|
||||
|
||||
### Public
|
||||
- `/` - Landing page
|
||||
- `/login` - Login form
|
||||
- `/register` - Registration form
|
||||
|
||||
### Authenticated
|
||||
- `/dashboard` - Due cards, progress widgets, recent activity, quick start
|
||||
- `/learn/[collectionId]` - Learning session with cards
|
||||
- `/collections` - List all collections (global + user's)
|
||||
- `/collections/[id]` - Collection detail, hanzi list, edit
|
||||
- `/collections/new` - Create collection
|
||||
- `/hanzi` - Search hanzi (filters, pagination)
|
||||
- `/hanzi/[id]` - Hanzi detail (all transcriptions, meanings, etc)
|
||||
- `/progress` - Charts, stats, session history
|
||||
- `/settings` - User preferences
|
||||
|
||||
### Admin
|
||||
- `/admin/collections` - Manage global collections
|
||||
- `/admin/hanzi` - Manage hanzi database
|
||||
- `/admin/import` - Import data (JSON/CSV upload)
|
||||
- `/admin/users` - User management
|
||||
|
||||
### Key UI Components
|
||||
- **LearningCard**: Large hanzi, 4 pinyin options in 2x2 grid, progress bar
|
||||
- **AnswerFeedback**: Green/red feedback, show correct answer, streak, removal suggestion
|
||||
- **CollectionCard**: Name, count, progress, quick actions
|
||||
- **DashboardWidgets**: Due cards, daily progress, streak, recent activity
|
||||
- **Charts**: Activity heatmap, accuracy line chart, HSK breakdown bar chart
|
||||
|
||||
### Design
|
||||
- Mobile-first responsive
|
||||
- Dark mode support
|
||||
- Tailwind CSS
|
||||
- Keyboard shortcuts (1-4 for answers, Space to continue)
|
||||
- WCAG 2.1 AA accessibility
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Import Formats
|
||||
|
||||
### HSK JSON (from github.com/drkameleon/complete-hsk-vocabulary)
|
||||
```json
|
||||
{
|
||||
"simplified": "爱好",
|
||||
"radical": "爫",
|
||||
"level": ["new-1", "old-3"],
|
||||
"frequency": 4902,
|
||||
"pos": ["n", "v"],
|
||||
"forms": [{
|
||||
"traditional": "愛好",
|
||||
"transcriptions": {
|
||||
"pinyin": "ài hào",
|
||||
"numeric": "ai4 hao4"
|
||||
},
|
||||
"meanings": ["to like; hobby"],
|
||||
"classifiers": ["个"]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### CSV Format
|
||||
```csv
|
||||
simplified,traditional,pinyin,meaning,hsk_level,radical,frequency,pos,classifiers
|
||||
爱好,愛好,ài hào,"to like; hobby",new-1,爫,4902,"n,v",个
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Testing Strategy
|
||||
|
||||
### Unit Tests (70% coverage target)
|
||||
- **SM-2 algorithm** - All calculation paths
|
||||
- **Card selection logic** - Sorting, filtering, limits
|
||||
- **Parsers** - JSON/CSV parsing, error handling
|
||||
- **Validation schemas** - Zod schemas
|
||||
|
||||
### Integration Tests (80% of Server Actions)
|
||||
- Auth actions with database
|
||||
- Learning flow (start session, submit answers, end session)
|
||||
- Collection CRUD
|
||||
- Import process
|
||||
|
||||
### E2E Tests (Critical paths)
|
||||
- Complete learning session
|
||||
- Create collection and add hanzi
|
||||
- Search hanzi
|
||||
- Admin import
|
||||
- Auth flow
|
||||
|
||||
**Tools:** Vitest (unit/integration), Playwright (E2E)
|
||||
|
||||
---
|
||||
|
||||
## 9. Development Milestones
|
||||
|
||||
### Week 1: Foundation
|
||||
- Setup Next.js 16 project
|
||||
- Configure Prisma + PostgreSQL
|
||||
- Setup Docker Compose
|
||||
- Create all data models
|
||||
- Configure NextAuth.js
|
||||
|
||||
### Week 2: Authentication
|
||||
- Registration/login pages
|
||||
- Middleware protection
|
||||
- User preferences
|
||||
- Integration tests
|
||||
|
||||
### Week 3-4: Data Import
|
||||
- Admin role middleware
|
||||
- HSK JSON parser
|
||||
- CSV parser
|
||||
- Import UI and actions
|
||||
- Test with real HSK data
|
||||
|
||||
### Week 5: Collections
|
||||
- Collections CRUD
|
||||
- Add/remove hanzi
|
||||
- Global HSK collections
|
||||
|
||||
### Week 5: Hanzi Search
|
||||
- Search page
|
||||
- Filters (HSK level)
|
||||
- Hanzi detail view
|
||||
- Pagination
|
||||
|
||||
### Week 6: SM-2 Algorithm
|
||||
- Implement algorithm
|
||||
- Card selection logic
|
||||
- Progress tracking
|
||||
- Unit tests (90%+ coverage)
|
||||
|
||||
### Week 7-8: Learning Interface
|
||||
- Learning session pages
|
||||
- Card component
|
||||
- Answer submission
|
||||
- Feedback UI
|
||||
- Session summary
|
||||
- Keyboard shortcuts
|
||||
- E2E tests
|
||||
|
||||
### Week 9: Dashboard & Progress
|
||||
- Dashboard widgets
|
||||
- Progress page
|
||||
- Charts (Recharts)
|
||||
- Statistics calculations
|
||||
|
||||
### Week 10: UI Polish
|
||||
- Responsive layouts
|
||||
- Mobile navigation
|
||||
- Dark mode
|
||||
- Loading/empty states
|
||||
- Toast notifications
|
||||
- Accessibility improvements
|
||||
|
||||
### Week 11: Testing & Docs
|
||||
- Complete test coverage
|
||||
- E2E tests for all critical flows
|
||||
- README and documentation
|
||||
- Security audit
|
||||
|
||||
### Week 12: Deployment
|
||||
- Production environment
|
||||
- Docker deployment
|
||||
- SSL certificates
|
||||
- Database backup
|
||||
- Import HSK data
|
||||
- Final testing
|
||||
|
||||
---
|
||||
|
||||
## 10. Docker Configuration
|
||||
|
||||
### docker-compose.yml
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports: ["80:80", "443:443"]
|
||||
volumes:
|
||||
- ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./docker/ssl:/etc/nginx/ssl:ro
|
||||
depends_on: [app]
|
||||
|
||||
app:
|
||||
build: .
|
||||
expose: ["3000"]
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://memohanzi_user:password@postgres:5432/memohanzi_db
|
||||
- NEXTAUTH_URL=https://yourdomain.com
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
environment:
|
||||
POSTGRES_USER: memohanzi_user
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: memohanzi_db
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U hanzi_user"]
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
# .env.local
|
||||
DATABASE_URL="postgresql://memohanzi_user:password@localhost:5432/memohanzi_db"
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32"
|
||||
NODE_ENV="development"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Security Checklist
|
||||
|
||||
- [ ] Passwords hashed with bcrypt (10 rounds)
|
||||
- [ ] Session tokens httpOnly, sameSite
|
||||
- [ ] CSRF protection (NextAuth.js)
|
||||
- [ ] Rate limiting (Nginx)
|
||||
- [ ] Input validation (Zod, server-side)
|
||||
- [ ] SQL injection prevented (Prisma)
|
||||
- [ ] XSS prevention (React escaping)
|
||||
- [ ] HTTPS enforced (Nginx)
|
||||
- [ ] Secure headers (Nginx)
|
||||
- [ ] Role-based access enforced server-side
|
||||
- [ ] No sensitive data in logs
|
||||
- [ ] Environment variables for secrets
|
||||
|
||||
---
|
||||
|
||||
## 12. Phase 2 Features
|
||||
|
||||
1. **Additional Languages** - Multi-language support for meanings
|
||||
2. **Learning Modes** - Radical identification, hanzi-to-meaning, meaning-to-hanzi, tone practice
|
||||
3. **Autocomplete Data** - Auto-fill missing hanzi info from APIs
|
||||
4. **User Suggestions** - Allow users to report/suggest corrections
|
||||
|
||||
---
|
||||
|
||||
## 13. Phase 3 Ideas
|
||||
|
||||
- Writing practice (stroke order validation)
|
||||
- Social features (public collections, sharing)
|
||||
- Gamification (streaks, badges, leaderboards)
|
||||
- Mobile apps (React Native)
|
||||
- Audio pronunciation
|
||||
- Example sentences
|
||||
- Advanced SRS algorithms
|
||||
|
||||
---
|
||||
|
||||
## 14. Quick Reference Commands
|
||||
|
||||
**Development:**
|
||||
```bash
|
||||
# Start
|
||||
docker-compose up
|
||||
npm run dev
|
||||
|
||||
# Database
|
||||
npx prisma migrate dev
|
||||
npx prisma db seed
|
||||
npx prisma studio
|
||||
|
||||
# Testing
|
||||
npm run test
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
**Production:**
|
||||
```bash
|
||||
# Deploy
|
||||
docker-compose up -d --build
|
||||
|
||||
# Monitor
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. Success Criteria (MVP)
|
||||
|
||||
**Technical:**
|
||||
- [ ] All tests passing (70%+ coverage)
|
||||
- [ ] Can import complete HSK vocabulary (5000+ hanzi)
|
||||
- [ ] Page load <2s
|
||||
- [ ] Learning session responsive (<100ms)
|
||||
- [ ] Mobile responsive
|
||||
|
||||
**Functional:**
|
||||
- [ ] Complete learning session works end-to-end
|
||||
- [ ] SM-2 algorithm calculates correctly
|
||||
- [ ] Progress tracking accurate
|
||||
- [ ] Collections management works
|
||||
- [ ] Search works efficiently
|
||||
|
||||
**User Experience:**
|
||||
- [ ] Can learn 20+ cards in 5-10 minutes
|
||||
- [ ] Interface intuitive
|
||||
- [ ] Daily use sustainable
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Priority Order
|
||||
1. Authentication (foundational)
|
||||
2. Data import (need data)
|
||||
3. Collections (organize learning)
|
||||
4. Search (browse data)
|
||||
5. Learning algorithm (core logic)
|
||||
6. Learning interface (user interaction)
|
||||
7. Progress tracking (motivation)
|
||||
8. Polish & deploy
|
||||
|
||||
### Critical Paths to Test
|
||||
1. Register → Login → Create Collection → Add Hanzi → Start Learning → Complete Session → View Progress
|
||||
2. Admin → Import HSK Data → Create Global Collection → User uses global collection
|
||||
3. Search Hanzi → View Detail → Add to Collection → Learn
|
||||
|
||||
### Key Implementation Files
|
||||
- `prisma/schema.prisma` - All data models
|
||||
- `src/lib/learning/sm2.ts` - SM-2 algorithm
|
||||
- `src/lib/learning/card-selector.ts` - Card selection
|
||||
- `src/lib/import/hsk-parser.ts` - Parse HSK JSON
|
||||
- `src/actions/learning.ts` - Learning Server Actions
|
||||
- `src/app/(app)/learn/[collectionId]/page.tsx` - Learning UI
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **HSK Data Source**: https://github.com/drkameleon/complete-hsk-vocabulary
|
||||
- **Next.js Docs**: https://nextjs.org/docs
|
||||
- **Prisma Docs**: https://www.prisma.io/docs
|
||||
- **NextAuth Docs**: https://authjs.dev
|
||||
- **SM-2 Algorithm**: https://www.supermemo.com/en/archives1990-2015/english/ol/sm2
|
||||
|
||||
---
|
||||
|
||||
**This specification is complete and ready for implementation with Claude Code.**
|
||||
|
||||
Start with Milestone 1 (Week 1: Foundation) and proceed sequentially through the milestones.
|
||||
Reference in New Issue
Block a user