From 76e8af4987dd8e1f980d8e8794ce2a7d28ad6fde Mon Sep 17 00:00:00 2001 From: Lasse Rune Hansen Date: Sun, 31 May 2026 18:20:53 +0200 Subject: [PATCH] Add complete solution: documentation, frontend, and project files - Add comprehensive documentation in docs/ (architecture, features, roadmap) - Add german-app-frontend with Vite, TypeScript, ESLint configuration - Add AGENTS.md and .gitignore Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- .gitignore | 30 + AGENTS.md | 534 ++++ docs/README.md | 98 + docs/ROADMAP.md | 546 ++++ docs/architecture/application-plan.md | 1541 ++++++++++++ docs/architecture/backend-structure.md | 61 + docs/architecture/frontend-structure.md | 119 + docs/database/initial-database-schema.sql | 242 ++ docs/development/how-to-run-vibe.md | 2 + docs/features/README.md | 373 +++ docs/features/ai-services.md | 896 +++++++ docs/features/frontend-ui.md | 605 +++++ docs/features/gamification.md | 416 ++++ docs/features/infrastructure-setup.md | 273 ++ docs/features/lesson-management.md | 361 +++ docs/features/quiz-system.md | 460 ++++ docs/features/story-integration.md | 411 +++ docs/features/template.md | 187 ++ docs/features/user-authentication.md | 325 +++ docs/features/vocabulary-system.md | 368 +++ german-app-frontend/.gitignore | 24 + german-app-frontend/README.md | 73 + german-app-frontend/eslint.config.js | 22 + german-app-frontend/index.html | 13 + german-app-frontend/package-lock.json | 2768 +++++++++++++++++++++ german-app-frontend/package.json | 30 + german-app-frontend/public/favicon.svg | 1 + german-app-frontend/public/icons.svg | 24 + german-app-frontend/tsconfig.app.json | 25 + german-app-frontend/tsconfig.json | 7 + german-app-frontend/tsconfig.node.json | 24 + german-app-frontend/vite.config.ts | 7 + 32 files changed, 10866 insertions(+) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 docs/README.md create mode 100644 docs/ROADMAP.md create mode 100644 docs/architecture/application-plan.md create mode 100644 docs/architecture/backend-structure.md create mode 100644 docs/architecture/frontend-structure.md create mode 100644 docs/database/initial-database-schema.sql create mode 100644 docs/development/how-to-run-vibe.md create mode 100644 docs/features/README.md create mode 100644 docs/features/ai-services.md create mode 100644 docs/features/frontend-ui.md create mode 100644 docs/features/gamification.md create mode 100644 docs/features/infrastructure-setup.md create mode 100644 docs/features/lesson-management.md create mode 100644 docs/features/quiz-system.md create mode 100644 docs/features/story-integration.md create mode 100644 docs/features/template.md create mode 100644 docs/features/user-authentication.md create mode 100644 docs/features/vocabulary-system.md create mode 100644 german-app-frontend/.gitignore create mode 100644 german-app-frontend/README.md create mode 100644 german-app-frontend/eslint.config.js create mode 100644 german-app-frontend/index.html create mode 100644 german-app-frontend/package-lock.json create mode 100644 german-app-frontend/package.json create mode 100644 german-app-frontend/public/favicon.svg create mode 100644 german-app-frontend/public/icons.svg create mode 100644 german-app-frontend/tsconfig.app.json create mode 100644 german-app-frontend/tsconfig.json create mode 100644 german-app-frontend/tsconfig.node.json create mode 100644 german-app-frontend/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8533c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Create .gitignore (if not already present) +echo "# Dependencies +node_modules/ +dist/ +build/ +*.log +.env +.env.local +.env.*.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Backend +bin/ +obj/ +*.user +*.suo +*.cache + +# Frontend +.coverage +" > .gitignore \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..baecda5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,534 @@ +# DeutschLernen Solution Development Instructions + +This file contains project-specific instructions, rules, and workflows for Mistral Vibe CLI when working on the **DeutschLernen** solution, which includes: +- **GermanApp** (Backend: ASP.NET Core Web API, .NET 9.0) +- **german-app-frontend** (Frontend: React 19, TypeScript, Vite) + +This solution follows **Clean Architecture** principles. + +--- + +## Solution Architecture: Clean Architecture + +The solution is structured according to **Uncle Bob's Clean Architecture** principles with the following layers: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ PRESENTATION LAYER │ +│ ┌─────────────────────┐ ┌───────────────────────────────┐ │ +│ │ german-app-frontend │ │ GermanApp Controllers │ │ +│ │ (React + TypeScript)│ │ (ASP.NET Core Minimal APIs)│ │ +│ └─────────────────────┘ └───────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ APPLICATION LAYER │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ Use Cases, Application Services, DTOs, Command/Query │ │ +│ │ - GermanApp/Application/ │ │ +│ │ - MediatR pattern for CQRS (recommended) │ │ +│ │ - Business logic orchestration │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ DOMAIN LAYER │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ Core Business Logic, Entities, Value Objects, Domain │ │ +│ │ Events, Domain Services │ │ +│ │ - GermanApp/Domain/ │ │ +│ │ - Pure C# (no framework dependencies) │ │ +│ │ - Contains enterprise-wide business rules │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ INFRASTRUCTURE LAYER │ +│ ┌─────────────────────┐ ┌───────────────────────────────┐ │ +│ │ Data Access │ │ External Services │ │ +│ │ - Repositories │ │ - Email, SMS, Payments │ │ +│ │ - Entity Framework │ │ - File Storage │ │ +│ │ - DbContext │ │ - Caching │ │ +│ └─────────────────────┘ └───────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### The Dependency Rule +**Inner layers MUST NOT depend on outer layers.** Dependencies flow INWARD: +- **Presentation** → **Application** → **Domain** +- **Infrastructure** → **Domain** (via interfaces defined in Domain/Application) +- **Infrastructure** → **Application** (implementations of interfaces) + +### Key Principles +1. **Single Responsibility**: Each class has one reason to change +2. **Open/Closed**: Open for extension, closed for modification +3. **Liskov Substitution**: Subtypes must be substitutable +4. **Interface Segregation**: Clients shouldn't depend on unused interfaces +5. **Dependency Inversion**: Depend on abstractions, not concretions + +--- + +## Project Structure + +### Backend: GermanApp (ASP.NET Core) +``` +GermanApp/ +├── Domain/ # Domain Layer (Pure C#) +│ ├── Entities/ # Business entities +│ ├── ValueObjects/ # Immutable value objects +│ ├── Enums/ # Domain enums +│ ├── Exceptions/ # Domain-specific exceptions +│ ├── Interfaces/ # Domain interfaces (repositories, services) +│ └── DomainEvents/ # Domain events +│ +├── Application/ # Application Layer +│ ├── UseCases/ # Use case handlers (CQRS pattern) +│ │ ├── Commands/ # Command handlers +│ │ └── Queries/ # Query handlers +│ ├── DTOs/ # Data Transfer Objects +│ ├── Interfaces/ # Application service interfaces +│ └── Services/ # Application services +│ +├── Infrastructure/ # Infrastructure Layer +│ ├── Data/ # Data persistence +│ │ ├── Repositories/ # Repository implementations +│ │ └── DbContext/ # Entity Framework context +│ ├── Services/ # External service implementations +│ └── Configurations/ # Dependency injection configs +│ +├── Presentation/ # Presentation Layer +│ ├── Controllers/ # API Controllers +│ ├── Endpoints/ # Minimal API endpoints +│ └── Models/ # API request/response models +│ +├── Shared/ # Shared utilities +│ ├── Constants/ # Application constants +│ ├── Extensions/ # Extension methods +│ └── Helpers/ # Helper classes +│ +├── Program.cs # Application entry point +├── appsettings.json # Configuration +└── Tests/ # Test projects + ├── Unit/ + │ ├── Domain/ + │ ├── Application/ + │ └── Infrastructure/ + └── Integration/ +``` + +### Frontend: german-app-frontend (React + TypeScript) +``` +german-app-frontend/ +├── public/ # Static files +├── src/ +│ ├── assets/ # Static assets (images, fonts) +│ ├── components/ # React components +│ │ ├── ui/ # Reusable UI components +│ │ ├── layout/ # Layout components +│ │ └── features/ # Feature-specific components +│ │ +│ ├── hooks/ # Custom React hooks +│ ├── lib/ # Utility functions, constants +│ │ └── api/ # API client, service calls +│ │ +│ ├── stores/ # State management +│ ├── types/ # TypeScript type definitions +│ │ └── api/ # API types (mirror backend DTOs) +│ │ +│ ├── pages/ # Page components +│ ├── routes/ # Routing configuration +│ │ +│ ├── styles/ # Global styles, themes +│ │ +│ ├── App.tsx # Main application component +│ └── main.tsx # Application entry point +│ +├── package.json +└── tsconfig.json +``` + +--- + +## Code Style & Conventions + +### Backend (C#) + +#### General C# Standards +- Use **PascalCase** for all public types, methods, properties, and constants +- Use **camelCase** for local variables, method parameters, and private fields +- Use **_camelCase** with underscore prefix for private fields +- Use **UPPER_SNAKE_CASE** for constants + +#### Formatting +- 4 spaces for indentation (no tabs) +- Opening braces on the same line (K&R style) +- One blank line between methods +- Line length: Keep under 120 characters when practical + +#### Naming Conventions +- **Interfaces**: Prefix with `I` (e.g., `IUserRepository`, `IEmailService`) +- **Async methods**: Suffix with `Async` +- **Records**: Use PascalCase for properties +- **Boolean properties**: Prefix with `Is`, `Has`, `Can`, or `Should` +- **Domain entities**: Use business terminology (e.g., `Lesson`, `VocabularyWord`) +- **Value objects**: Use descriptive names (e.g., `EmailAddress`, `GermanWord`) + +#### Clean Architecture Specific +- **Domain Layer**: Pure C# with NO framework dependencies + - No `using Microsoft.AspNetCore...` + - No `using System.Web...` + - Only primitive types or other domain types +- **Interfaces in Domain/Application**: Define interfaces where they are USED +- **Implementations in Infrastructure**: Implement interfaces defined in inner layers +- **DTOs**: Use `Record` types for immutable DTOs +- **Entities**: Use classes with private setters for mutable entities + +#### .NET Specific +- Use **nullable reference types** (enabled in project) +- Prefer **records** over classes for DTOs and immutable data +- Use **Minimal APIs** style for simple endpoints +- Use **explicit typing** for public APIs, **var** for local variables +- Use **File-scoped namespaces** + +### Frontend (TypeScript/React) + +#### TypeScript +- Use **PascalCase** for types, interfaces, classes +- Use **camelCase** for variables, functions, properties +- Use **SCREAMING_SNAKE_CASE** for constants +- Use **ISomething** prefix for interfaces (optional but consistent) + +#### React +- **Components**: PascalCase (e.g., `UserCard`, `LessonList`) +- **Hooks**: Use `use` prefix (e.g., `useUser`, `useLessons`) +- **Props**: Use TypeScript interfaces for prop types +- **Events**: Use `on` prefix for event handlers (e.g., `onClick`, `onSubmit`) + +#### File Naming +- Components: `ComponentName.tsx` +- Hooks: `useHookName.ts` +- Utilities: `utilityName.ts` +- Types: `types.ts` or `TypeName.ts` +- Constants: `constants.ts` + +#### API Client +- Use **axios** or **fetch** with a centralized API client +- Mirror backend DTO types in `src/types/api/` +- Use consistent naming: `getUser`, `createLesson`, `updateVocabularyWord` + +--- + +## Testing Requirements + +### Backend (MsTest) +- **Framework**: MsTest (required) +- **Coverage**: All business logic must have unit tests +- **Integration tests**: For API endpoints and database interactions + +#### Test Structure +``` +Tests/ +├── Unit/ +│ ├── Domain/ +│ │ └── Entities/ +│ │ └── UserTests.cs +│ ├── Application/ +│ │ └── UseCases/ +│ │ └── CreateUserCommandHandlerTests.cs +│ └── Infrastructure/ +│ └── Repositories/ +│ └── UserRepositoryTests.cs +│ +└── Integration/ + ├── Api/ + │ └── WeatherForecastEndpointTests.cs + └── Database/ + └── UserRepositoryIntegrationTests.cs +``` + +#### Test Naming (Backend) +- Test classes: `[ClassUnderTest]Tests` +- Test methods: `MethodName_Scenario_ExpectedBehavior` + +### Frontend (Vitest) +- **Framework**: Vitest (recommended for Vite projects) +- **Component tests**: Test user interactions and rendering +- **Unit tests**: Test utility functions and hooks + +#### Test Naming (Frontend) +- Test files: `*.test.tsx` or `*.test.ts` +- Test blocks: `describe('ComponentName', () => {...})` +- Test cases: `it('should do something when condition', () => {...})` + +### Test Quality (Both) +- Follow Arrange-Act-Assert pattern +- Each test should test ONE thing +- Use meaningful test data, not magic numbers/strings +- Mock external dependencies (API calls, database, etc.) +- Keep tests fast and isolated + +--- + +## Git Workflow + +### Branching Strategy: GitHub Flow +1. **main** - Production-ready code (protected) +2. **feature/*** - Feature branches created from main +3. **bugfix/*** - Bug fix branches created from main +4. **refactor/*** - Refactoring branches + +### Branch Naming +- `feature/[short-description]` - e.g., `feature/add-user-authentication` +- `bugfix/[short-description]` - e.g., `bugfix/lesson-load-error` +- `refactor/[short-description]` - e.g., `refactor/domain-entities` +- `docs/[short-description]` - For documentation changes +- Use kebab-case (lowercase with hyphens) + +### Commit Conventions: Conventional Commits +``` +type(scope): description +``` + +**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf` + +**Scope**: Should indicate the layer or project: +- `feat(backend):` or `feat(domain):` or `feat(api):` +- `feat(frontend):` or `feat(ui):` + +**Examples:** +``` +feat(backend/domain): add User entity with validation +feat(frontend/ui): create LessonCard component +fix(backend/api): handle null values in weather endpoint +refactor(backend/application): extract user creation use case +``` + +### Pull Requests +- All changes must go through a Pull Request +- PR title should follow Conventional Commits format +- PR description should include: + - What changed + - Why it changed + - Screenshots/GIFs for UI changes + - Which Clean Architecture layer(s) were affected +- Minimum 1 approval required +- All CI checks must pass before merging + +--- + +## Development Workflow + +### Backend Commands +```bash +# Build +dotnet build + +# Run +dotnet run --project GermanApp + +# Tests +dotnet test +dotnet test /p:CollectCoverage=true + +# Add package +dotnet add GermanApp package PackageName +``` + +### Frontend Commands +```bash +# Install +npm install + +# Dev server +npm run dev + +# Build +npm run build + +# Lint +npm run lint + +# Tests +npm run test +``` + +### Before Pushing +1. Run all tests (backend and frontend) +2. Check for lint/warnings +3. Review changes: `git diff main` +4. Ensure no Clean Architecture violations +5. Squash commits if needed + +--- + +## Clean Architecture Implementation Guidelines + +### Domain Layer (Pure C#) +- **NO** framework dependencies +- Contains: Entities, Value Objects, Domain Events, Domain Services, Interfaces +- Entities should have identity (Id) and business behavior +- Value Objects should be immutable + +### Application Layer +- Contains: Use Cases, DTOs, Application Services, Interfaces +- Orchestrates domain objects to fulfill use cases +- Defines interfaces for infrastructure (repositories, services) +- Uses MediatR pattern for CQRS (recommended) + +### Infrastructure Layer +- Contains: Repository implementations, DbContext, External services +- Implements interfaces defined in Domain/Application layers +- Entity Framework Core for data access +- External service adapters (email, file storage, etc.) + +### Presentation Layer +- **Backend**: Minimal APIs, Controllers, API Models +- **Frontend**: React components, hooks, state management +- Thin layer that handles presentation concerns only +- Delegates business logic to Application layer + +### Dependency Injection Setup +```csharp +// In Program.cs + +// Domain layer - no registration needed (pure logic) + +// Application layer +builder.Services.AddApplicationServices(); + +// Infrastructure layer +builder.Services.AddInfrastructureServices(configuration); + +// Presentation layer +builder.Services.AddPresentationServices(); +``` + +--- + +## Cross-Cutting Concerns + +### API Contracts +- **Backend**: Define API models in Presentation/Models +- **Frontend**: Mirror these types in src/types/api/ +- Keep API contracts stable and versioned +- Use DTOs for all API communication (never domain entities directly) + +### Error Handling +- **Domain**: Throw domain-specific exceptions +- **Application**: Handle domain exceptions, map to application errors +- **Presentation**: Map to appropriate HTTP status codes +- **Frontend**: Display user-friendly error messages + +### Validation +- **Domain**: Entity/Value Object validation (business rules) +- **Application**: Use case validation (cross-entity rules) +- **Presentation**: Request model validation (FluentValidation) +- **Frontend**: Client-side validation (Zod, Yup, or similar) + +--- + +## Feature Implementation & Tracking + +For complex features, use the **Feature Tracking System** in `docs/features/`: + +### Feature Documentation Structure +``` +docs/features/ +├── README.md # Feature tracking overview & workflow +├── template.md # Template for new feature implementation plans +└── [feature-name].md # Individual feature files with: + # - Status (Planned/In Progress/Code Review/Completed) + # - Priority & Complexity + # - Requirements & Technical Design + # - Implementation Plan & Tasks + # - Dependencies & Notes +``` + +### Workflow +1. **Create**: Copy `template.md` → `[feature-name].md`, fill in details +2. **Plan**: Set status to `⏳ Planned`, add to `README.md` table +3. **Develop**: Update status to `🚀 In Progress`, check off tasks +4. **Review**: Set status to `🔄 Code Review`, link PR in feature file +5. **Complete**: Set status to `✅ Completed`, document lessons learned + +### Status Icons +- ⏳ Planned - Feature defined, not started +- 🚀 In Progress - Actively being developed +- 🔄 Code Review - PR submitted, awaiting review +- ✅ Completed - Feature done and deployed +- ❌ Blocked - Cannot proceed due to dependencies + +### Best Practices +- Create a feature file **before** starting development +- Update the file **as you work** (tasks, notes, decisions) +- Be **specific** with tasks (not "implement X", but "create Y service", "add Z endpoint") +- Document **design decisions** and **lessons learned** +- Link to **PRs, commits, and code** in the feature file + +--- + +## Important Notes + +- **Clean Architecture is mandatory**: Always check dependencies flow inward +- **MsTest required for backend**: All changes must include tests +- **Conventional Commits**: Follow strictly for all commits +- **Automatic deployment**: Merging to main deploys to production +- **Cross-project consistency**: Keep frontend and backend in sync +- **Feature tracking**: Use `docs/features/` for complex features + +--- + +*Last updated: May 31, 2025* +*This file can be updated as project conventions evolve.* +rn new User + { + Username = username, + Email = email, + CreatedAt = DateTime.UtcNow + }; + } + + // Domain behavior + public void ChangeEmail(Email newEmail) + { + Email = newEmail; + } +} +``` + +#### Value Object Example +```csharp +namespace GermanApp.Domain.ValueObjects; + +public record Email +{ + public string Value { get; } + + public Email(string value) + { + if (!IsValid(value)) + throw new DomainException("Invalid email format"); + Value = value; + } + + public static bool IsValid(string email) + { + // Validation logic + return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"); } +} +``` + +--- + +## Important Notes + +- **Clean Architecture is mandatory**: Always check dependencies flow inward +- **MsTest required for backend**: All changes must include tests +- **Conventional Commits**: Follow strictly for all commits +- **Automatic deployment**: Merging to main deploys to production +- **Cross-project consistency**: Keep frontend and backend in sync + +--- + +*Last updated: May 31, 2025* +*This file can be updated as project conventions evolve.* diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..617d811 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,98 @@ +# DeutschLernen Documentation + +Welcome to the documentation for the **DeutschLernen** solution. This directory contains all project documentation organized by category. + +--- + +## 📚 Documentation Structure + +``` +docs/ +├── README.md # This file - Table of Contents +│ +├── architecture/ # System architecture & design +│ ├── application-plan.md # High-level application plan and vision +│ ├── backend-structure.md # Backend architecture and component structure +│ └── frontend-structure.md # Frontend architecture and component structure +│ +├── database/ # Database-related documentation +│ └── initial-database-schema.sql # Initial SQL schema for the database +│ +└── development/ # Development guides & setup + └── how-to-run-vibe.md # Guide for running Vibe CLI in this project +``` + +--- + +## 📖 Documentation Categories + +### Architecture 🏗️ + +| File | Description | Last Updated | +|------|-------------|---------------| +| [application-plan.md](./architecture/application-plan.md) | Overall application vision, features, and roadmap | - | +| [backend-structure.md](./architecture/backend-structure.md) | Backend architecture, Clean Architecture layers, component diagrams | - | +| [frontend-structure.md](./architecture/frontend-structure.md) | Frontend architecture, React component structure, state management | - | + +### Database 🗃️ + +| File | Description | Last Updated | +|------|-------------|---------------| +| [initial-database-schema.sql](./database/initial-database-schema.sql) | SQL schema for database initialization, tables, relationships | - | + +### Development 🛠️ + +| File | Description | Last Updated | +|------|-------------|---------------| +| [how-to-run-vibe.md](./development/how-to-run-vibe.md) | Instructions for setting up and using Vibe CLI with this project | - | + +### Feature Tracking +| File | Description | Last Updated | +|------|-------------|---------------| +| [features/README.md](./features/README.md) | Feature implementation plans and progress tracking | - | +| [features/template.md](./features/template.md) | Template for creating new feature plans | - | + +--- + +## 📝 Adding New Documentation + +When adding new documentation files, please follow these guidelines: + +1. **Choose the right category**: Place files in the appropriate subdirectory +2. **Use kebab-case naming**: `new-document.md` not `NewDocument.md` +3. **Update this README**: Add your file to the appropriate table above +4. **Keep it organized**: Create new subdirectories if a category grows too large + +### Suggested Categories + +- `architecture/` - System design, component diagrams, technical decisions +- `database/` - Schema, migrations, data models +- `development/` - Setup guides, development workflows, tooling +- `api/` - API documentation, endpoints, contracts (if not using Swagger) +- `testing/` - Test strategies, test data, test cases +- `deployment/` - Deployment guides, CI/CD, infrastructure +- `features/` - Feature specifications, user stories + +--- + +## 🔍 Quick Links + +- **[Application Plan](./architecture/application-plan.md)** - Start here for project vision +- **[Backend Structure](./architecture/backend-structure.md)** - Clean Architecture implementation +- **[Database Schema](./database/initial-database-schema.sql)** - Database design +- **[Vibe Setup](./development/how-to-run-vibe.md)** - Development environment setup +- **[Feature Tracking](./features/README.md)** - Feature implementation plans & progress + +--- + +## 💡 Tips + +- Use the search functionality in your code editor to find specific topics across all docs +- Documentation should be **just enough** - focus on the "why" not just the "what" +- Keep documentation **up to date** with code changes +- For API documentation, rely on **Swagger/OpenAPI** where possible + +--- + +*Last updated: May 31, 2025* +*Need help? Check the [AGENTS.md](../AGENTS.md) file for development rules and workflows* diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..8beef93 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,546 @@ +# DeutschLernen Development Roadmap + +This document provides a comprehensive **development roadmap** for the DeutschLernen project, based on the feature plans in `docs/features/`. It outlines the implementation phases, timelines, resource allocation, and milestones for delivering the complete German learning application. + +--- + +## 🎯 Project Overview + +**DeutschLernen** is a comprehensive German language learning web application with: +- Structured lessons following CEFR levels (A1-C1) +- AI-powered features (story generation, speech recognition, text-to-speech) +- Interactive exercises and quizzes +- Gamification (points, badges, streaks) +- Progress tracking and analytics + +**Target Users:** Self-learners of German at all levels (A1–C1) + +**Technology Stack:** .NET 9.0 (Backend) + React 19 + TypeScript (Frontend) + PostgreSQL + AI Services + +--- + +## 🗺️ Implementation Phases + +The project is divided into **4 phases** based on dependencies and priority: + +--- + +## Phase 1: Foundation 🏗️ +**Duration:** 2 weeks | **Total Hours:** 30-42h | **Priority:** Critical + +### Objective +Establish the technical foundation for the entire application, including backend project, database, and authentication system. + +### Features +| # | Feature | Description | Hours | Status | Dependencies | +|---|---------|-------------|-------|--------|--------------| +| 1.1 | [Infrastructure Setup](features/infrastructure-setup.md) | .NET project, PostgreSQL, Docker, CI/CD | 10-14h | ⏳ Planned | None | +| 1.2 | [User Authentication](features/user-authentication.md) | JWT-based auth with ASP.NET Core Identity | 4-6h | ⏳ Planned | 1.1 | + +### Deliverables +- ✅ Working .NET 9.0 backend project +- ✅ PostgreSQL database with schema +- ✅ Docker containers for all services +- ✅ GitHub Actions CI/CD pipeline +- ✅ Authentication endpoints (register, login, logout) +- ✅ JWT token generation and validation +- ✅ Development environment setup guide + +### Success Criteria +- Backend project builds and runs successfully +- Database is accessible and migrations work +- Docker Compose spins up all services +- CI/CD pipeline passes on push +- Authentication flow works end-to-end + +### Week 1 +| Day | Developer | Tasks | Hours | +|-----|-----------|-------|-------| +| 1 | Backend | Initialize .NET project, configure appsettings | 4 | +| 1 | Backend | Set up Swagger/OpenAPI, CORS, logging | 2 | +| 2 | Backend | Configure PostgreSQL, create initial schema | 3 | +| 2 | Backend | Set up EF Core, create first migration | 2 | +| 3 | Backend | Create Dockerfile for backend | 2 | +| 3 | DevOps | Create docker-compose.yml | 2 | +| 4 | Backend | Create GitHub Actions workflow | 3 | +| 4 | Backend | Test complete infrastructure | 2 | +| 5 | Backend | Start User Authentication feature | 4 | + +### Week 2 +| Day | Developer | Tasks | Hours | +|-----|-----------|-------|-------| +| 6 | Backend | Create User entity and DTOs | 3 | +| 6 | Backend | Configure ASP.NET Core Identity | 2 | +| 7 | Backend | Create AuthService with JWT | 3 | +| 7 | Backend | Create AuthController endpoints | 2 | +| 8 | Backend | Implement token validation middleware | 2 | +| 8 | Backend | Add authorization to endpoints | 2 | +| 9 | Backend | Write unit tests for AuthService | 2 | +| 9 | Backend | Write integration tests for AuthController | 2 | +| 10 | Backend | Test authentication flow end-to-end | 2 | +| 10 | Backend | Finalize and merge Phase 1 | 2 | + +--- + +## Phase 2: Core Backend 🎯 +**Duration:** 2 weeks | **Total Hours:** 42-58h | **Priority:** Critical + +### Objective +Implement the core backend functionality, including lesson management, AI services integration, vocabulary system, and quiz system. + +### Features +| # | Feature | Description | Hours | Status | Dependencies | +|---|---------|-------------|-------|--------|--------------| +| 2.1 | [Lesson Management](features/lesson-management.md) | Lessons, levels, progress tracking | 10-16h | ⏳ Planned | Phase 1 | +| 2.2 | [AI Services](features/ai-services.md) | Mistral, Vosk, Coqui TTS integration | 10-16h | ⏳ Planned | Phase 1 | +| 2.3 | [Vocabulary System](features/vocabulary-system.md) | Word storage, audio, import | 8-12h | ⏳ Planned | Phase 1, 2.1 | +| 2.4 | [Quiz System](features/quiz-system.md) | Multiple question types, scoring | 6-10h | ⏳ Planned | Phase 1, 2.1 | + +### Deliverables +- ✅ Complete CEFR level and lesson management +- ✅ User progress tracking with sequential unlocking +- ✅ Mistral-Medium API integration for story generation +- ✅ Vosk speech recognition integration +- ✅ Coqui TTS integration for audio generation +- ✅ Vocabulary CRUD with article tracking +- ✅ Bulk vocabulary import from Goethe/DW +- ✅ Quiz CRUD with 4 question types +- ✅ Quiz scoring and pass/fail determination +- ✅ Progress tracking on quiz completion + +### Success Criteria +- All lesson content can be managed +- AI services generate stories, recognize speech, and generate audio +- Vocabulary system with audio works end-to-end +- Quiz system with all question types works +- Progress tracking updates correctly + +### Week 3 +| Day | Developer | Tasks | Hours | Feature | +|-----|-----------|-------|-------|---------| +| 11 | Backend | Create Level/Lesson entities | 3 | 2.1 | +| 11 | Backend | Create UserProgress entity | 2 | 2.1 | +| 12 | Backend | Create repositories | 3 | 2.1 | +| 12 | Backend | Create services | 3 | 2.1 | +| 13 | Backend | Create controllers | 2 | 2.1 | +| 13 | Backend | Implement unlocking logic | 2 | 2.1 | +| 14 | Backend | Write tests for Lesson Mgmt | 3 | 2.1 | +| 14 | Backend | Start AI Services | 2 | 2.2 | + +### Week 4 +| Day | Developer | Tasks | Hours | Feature | +|-----|-----------|-------|-------|---------| +| 15 | Backend | Configure Mistral API client | 3 | 2.2 | +| 15 | Backend | Implement retry/rate limiting | 2 | 2.2 | +| 16 | Backend | Set up Vosk Python environment | 3 | 2.2 | +| 16 | Backend | Create VoskService | 3 | 2.2 | +| 17 | Backend | Set up Coqui TTS environment | 2 | 2.2 | +| 17 | Backend | Create TtsService | 3 | 2.2 | +| 18 | Backend | Create health checks | 2 | 2.2 | +| 18 | Backend | Test AI Services | 2 | 2.2 | +| 19 | Backend | Start Vocabulary System | 4 | 2.3 | +| 20 | Backend | Start Quiz System | 4 | 2.4 | + +--- + +## Phase 3: Content & Features 📚 +**Duration:** 2 weeks | **Total Hours:** 30-42h | **Priority:** High + +### Objective +Implement the content management features (story integration, gamification) that build on the core backend. + +### Features +| # | Feature | Description | Hours | Status | Dependencies | +|---|---------|-------------|-------|--------|--------------| +| 3.1 | [Story Integration](features/story-integration.md) | AI-generated stories with audio | 8-12h | ⏳ Planned | Phase 2 | +| 3.2 | [Gamification](features/gamification.md) | Points, badges, streaks | 6-8h | ⏳ Planned | Phase 2 | + +### Deliverables +- ✅ Story generation per level using Mistral +- ✅ Story segmentation per lesson +- ✅ Story audio generation using Coqui TTS +- ✅ Sequential story unlocking with lesson completion +- ✅ Word click-to-translate functionality +- ✅ Points system for lessons and quizzes +- ✅ Badge system with automatic awarding +- ✅ Daily streak tracking +- ✅ User dashboard with gamification display + +### Success Criteria +- Stories generate correctly with level-appropriate vocabulary +- Story audio is clear and accessible +- Stories unlock sequentially as users complete lessons +- Points and badges are awarded correctly +- Streaks track daily activity accurately + +### Week 5 +| Day | Developer | Tasks | Hours | Feature | +|-----|-----------|-------|-------|---------| +| 21 | Backend | Create StorySegment entity | 2 | 3.1 | +| 21 | Backend | Create StoryService | 3 | 3.1 | +| 22 | Backend | Create StoryGenerationService | 3 | 3.1 | +| 22 | Backend | Implement Mistral integration | 2 | 3.1 | +| 23 | Backend | Create StoryController | 2 | 3.1 | +| 23 | Backend | Generate audio for stories | 3 | 3.1 | +| 24 | Backend | Test Story Integration | 2 | 3.1 | +| 24 | Backend | Start Gamification | 2 | 3.2 | + +### Week 6 +| Day | Developer | Tasks | Hours | Feature | +|-----|-----------|-------|-------|---------| +| 25 | Backend | Update User entity with gamification fields | 2 | 3.2 | +| 25 | Backend | Create Badge/UserBadge entities | 2 | 3.2 | +| 26 | Backend | Create PointsService | 2 | 3.2 | +| 26 | Backend | Create BadgeService | 3 | 3.2 | +| 27 | Backend | Create StreakService | 2 | 3.2 | +| 27 | Backend | Create GamificationController | 2 | 3.2 | +| 28 | Backend | Write tests for Gamification | 2 | 3.2 | +| 28 | Backend | Finalize Phase 3 | 2 | Both | + +--- + +## Phase 4: Frontend 🎨 +**Duration:** 2 weeks | **Total Hours:** 10-16h | **Priority:** High + +### Objective +Implement the complete React + TypeScript frontend application with all UI components, pages, and services. + +### Features +| # | Feature | Description | Hours | Status | Dependencies | +|---|---------|-------------|-------|--------|--------------| +| 4.1 | [Frontend UI](features/frontend-ui.md) | Complete React frontend | 10-16h | ⏳ Planned | Phase 2-3 | + +### Deliverables +- ✅ React 19 + TypeScript + Vite project setup +- ✅ Tailwind CSS styling +- ✅ All shared components (Button, Input, Card, AudioPlayer, Recorder, Modal, Notification) +- ✅ Authentication pages (Login, Register, Profile) +- ✅ Dashboard page with progress tracking +- ✅ Lesson pages with all tabs (Vocabulary, Grammar, Story, Reading, Listening, Speaking, Writing, Quiz) +- ✅ Quiz pages with all question types +- ✅ Story pages with audio playback +- ✅ Practice pages for speaking/writing +- ✅ API client with Axios +- ✅ Authentication context and protected routes +- ✅ State management with Zustand +- ✅ Responsive design for mobile, tablet, desktop +- ✅ Accessibility compliance (WCAG 2.1 AA) + +### Success Criteria +- All pages load without errors +- All components render correctly +- Authentication flow works end-to-end +- Audio playback and recording work on all supported browsers +- Responsive design works on all screen sizes +- Application is accessible to screen readers and keyboard users + +### Week 7 +| Day | Developer | Tasks | Hours | Feature | +|-----|-----------|-------|-------|---------| +| 29 | Frontend | Initialize Vite + React + TypeScript project | 3 | 4.1 | +| 29 | Frontend | Install dependencies (Router, Tailwind, Axios, Zustand) | 2 | 4.1 | +| 30 | Frontend | Create API client with interceptors | 2 | 4.1 | +| 30 | Frontend | Create AuthContext and protected routes | 3 | 4.1 | +| 31 | Frontend | Create Layout components (Header, Footer) | 3 | 4.1 | +| 31 | Frontend | Create shared UI components | 3 | 4.1 | +| 32 | Frontend | Create Auth pages (Login, Register, Profile) | 4 | 4.1 | + +### Week 8 +| Day | Developer | Tasks | Hours | Feature | +|-----|-----------|-------|-------|---------| +| 33 | Frontend | Create Dashboard page | 3 | 4.1 | +| 33 | Frontend | Create Lesson page with tabs | 4 | 4.1 | +| 34 | Frontend | Create Quiz pages and components | 4 | 4.1 | +| 34 | Frontend | Create Story pages and components | 3 | 4.1 | +| 35 | Frontend | Create Practice pages | 3 | 4.1 | +| 35 | Frontend | Add loading states and error handling | 2 | 4.1 | +| 36 | Frontend | Responsive design tweaks | 3 | 4.1 | +| 37 | Frontend | Accessibility audit | 2 | 4.1 | +| 38 | Frontend | Performance optimization | 2 | 4.1 | +| 38 | Frontend | Final testing and polish | 2 | 4.1 | + +--- + +## 📅 Project Timeline Summary + +| Phase | Duration | Hours | Features | Status | +|-------|----------|-------|----------|--------| +| Phase 1: Foundation | 2 weeks | 30-42h | 2 | ⏳ Planned | +| Phase 2: Core Backend | 2 weeks | 42-58h | 4 | ⏳ Planned | +| Phase 3: Content & Features | 2 weeks | 30-42h | 2 | ⏳ Planned | +| Phase 4: Frontend | 2 weeks | 10-16h | 1 | ⏳ Planned | +| **Total** | **8 weeks** | **112-158h** | **9** | ⏳ Planned | + +**For a small team (2-3 developers):** ~4-5 weeks +**For a solo developer:** ~8-10 weeks +**For a larger team (4+ developers):** ~3-4 weeks + +--- + +## 📊 Resource Allocation Scenarios + +### Scenario 1: Solo Developer (8-10 weeks) + +``` +Week 1-2: Phase 1 - Foundation (30-42h) +Week 3-4: Phase 2 - Core Backend (42-58h) +Week 5-6: Phase 3 - Content & Features (30-42h) +Week 7-8: Phase 4 - Frontend (10-16h) +Week 9-10: Testing, Polish, Bug Fixes (20h) +``` + +**Total:** ~152-178 hours across 10 weeks + +### Scenario 2: Team of 2 (4-5 weeks) + +**Developer A (Backend Focus):** +- Week 1: Infrastructure (14h) + Auth (4h) = 18h +- Week 2: Lesson Mgmt (14h) + AI Services (8h) = 22h +- Week 3: Vocabulary (10h) + Quiz (8h) = 18h +- Week 4: Story (10h) + Gamification (6h) = 16h +- Week 5: Frontend support + Testing = 16h + +**Developer B (Full-Stack):** +- Week 1: Auth (2h) + Lesson Mgmt (2h) = 4h +- Week 2: AI Services (8h) + Vocabulary (2h) = 10h +- Week 3: Quiz (2h) + Story (2h) + Gamification (2h) = 6h +- Week 4: Frontend (16h) = 16h +- Week 5: Frontend (16h) + Testing = 16h + +**Total:** ~142-158 hours across 5 weeks + +### Scenario 3: Team of 3 (3-4 weeks) + +**Developer A (Backend Lead):** +- Infrastructure, Auth, Lesson Mgmt, AI Services + +**Developer B (Backend):** +- Vocabulary, Quiz, Story, Gamification + +**Developer C (Frontend):** +- Wait for Phase 2, then build entire frontend + +**Total:** ~112-158 hours across 3-4 weeks + +--- + +## 🎯 Milestones & Checkpoints + +### Milestone 1: Foundation Complete (End of Week 2) +**Success Metrics:** +- [ ] Backend project builds and runs +- [ ] Database is configured and accessible +- [ ] Docker containers work +- [ ] CI/CD pipeline passes +- [ ] Authentication works end-to-end +- [ ] Can start any Phase 2 feature + +**Exit Criteria:** +- All Phase 1 acceptance criteria met +- All Phase 1 tests passing +- All Phase 1 documentation complete + +### Milestone 2: Core Backend Complete (End of Week 4) +**Success Metrics:** +- [ ] Lesson management works +- [ ] AI services integrate successfully +- [ ] Vocabulary system works with audio +- [ ] Quiz system works with all question types +- [ ] Progress tracking updates correctly +- [ ] Can start any Phase 3 feature + +**Exit Criteria:** +- All Phase 2 acceptance criteria met +- All Phase 2 tests passing +- All Phase 2 documentation complete + +### Milestone 3: Content & Features Complete (End of Week 6) +**Success Metrics:** +- [ ] Story generation and audio works +- [ ] Stories unlock sequentially +- [ ] Gamification (points, badges, streaks) works +- [ ] Can start Frontend implementation + +**Exit Criteria:** +- All Phase 3 acceptance criteria met +- All Phase 3 tests passing +- All Phase 3 documentation complete + +### Milestone 4: Frontend Complete (End of Week 8) +**Success Metrics:** +- [ ] All pages render without errors +- [ ] All components work correctly +- [ ] Authentication flow works +- [ ] Audio playback and recording works +- [ ] Responsive design works +- [ ] Application is production-ready + +**Exit Criteria:** +- All Phase 4 acceptance criteria met +- All Phase 4 tests passing +- All Phase 4 documentation complete + +### Milestone 5: Production Deployment (End of Week 9-10) +**Success Metrics:** +- [ ] All features deployed to production +- [ ] Performance meets targets +- [ ] Security review complete +- [ ] User testing successful +- [ ] Bug count is acceptable + +**Exit Criteria:** +- Application is live and accessible +- Users can register, login, and use all features +- No critical bugs +- Performance is acceptable + +--- + +## 🚨 Critical Path Analysis + +The **critical path** (longest sequence of dependent tasks) is: + +``` +Infrastructure Setup (10-14h) + ↓ +User Authentication (4-6h) + ↓ +Lesson Management (10-16h) + ↓ +Vocabulary System (8-12h) + Quiz System (6-10h) [Parallel] + ↓ +Story Integration (8-12h) + Gamification (6-8h) [Parallel] + ↓ +Frontend UI (10-16h) +``` + +**Total Critical Path: 56-82 hours** (~7-10 days) + +This means the **minimum project duration** is determined by this path, even with parallel development on other features. + +--- + +## 📈 Risk Management + +### Top 5 Project Risks + +| # | Risk | Likelihood | Impact | Mitigation | Owner | Phase | +|---|------|------------|--------|------------|-------|-------| +| 1 | Python-.NET integration failures | High | High | Use Process.Start initially, implement proper error handling | Backend | 2 | +| 2 | AI service performance issues | High | Medium | Implement async processing, rate limiting, caching | Backend | 2 | +| 3 | Vosk/Coqui model compatibility | Medium | High | Test with models before implementation, have fallbacks | Backend | 2 | +| 4 | Mistral API costs exceed budget | Medium | High | Monitor usage, cache aggressively, set budget alerts | Product | 2 | +| 5 | Scope creep (adding more features) | Medium | Medium | Stick to MVP scope, defer nice-to-haves | PM | All | + +### Risk Monitoring + +**Daily:** Check AI service health and costs +**Weekly:** Review progress against roadmap +**Phase End:** Conduct risk review before starting next phase + +--- + +## 🛠️ Tooling & Environment + +### Development Environment +- **OS:** Linux (recommended), Windows 10/11, or macOS +- **Backend:** .NET 9.0 SDK +- **Frontend:** Node.js 18+, npm/yarn +- **Database:** PostgreSQL 15+ +- **AI Services:** + - Python 3.8+ + - Vosk + vosk-model-de-0.22 (~500MB) + - Coqui TTS + German model (~1.5GB) +- **Docker:** Docker Desktop or Docker Engine +- **IDE:** Visual Studio Code, Rider, or Visual Studio + +### DevOps +- **Version Control:** Git + GitHub +- **CI/CD:** GitHub Actions +- **Containerization:** Docker + Docker Compose +- **Monitoring:** Health checks, logging + +--- + +## 🎓 Team Onboarding + +### New Team Member Checklist + +1. **Environment Setup** (Day 1) + - [ ] Clone repository + - [ ] Install .NET 9.0 SDK + - [ ] Install Node.js 18+ + - [ ] Install PostgreSQL + - [ ] Install Docker + - [ ] Install Python 3.8+ + - [ ] Download Vosk model + - [ ] Download Coqui TTS model + - [ ] Run `docker-compose up` + - [ ] Verify all services are running + +2. **Project Familiarization** (Day 2) + - [ ] Read AGENTS.md + - [ ] Read Application Plan + - [ ] Review Clean Architecture structure + - [ ] Review current feature plans + - [ ] Understand development workflow + +3. **First Task** (Day 3) + - [ ] Pick a "good first issue" from backlog + - [ ] Create feature branch + - [ ] Implement with tests + - [ ] Submit PR for review + +--- + +## 📊 Success Metrics + +### Quality Metrics +| Metric | Target | Measurement | +|--------|--------|-------------| +| Code Coverage | 80%+ | SonarQube / Coverlet | +| Test Pass Rate | 100% | CI/CD Pipeline | +| Build Success Rate | 99% | CI/CD Pipeline | +| Deployment Frequency | Daily | GitHub Actions | +| Mean Time to Repair | <4h | Incident Tracking | + +### Performance Metrics +| Metric | Target | Measurement | +|--------|--------|-------------| +| Backend Response Time | <500ms | Health Checks | +| Frontend Load Time | <2s | Lighthouse | +| API Latency (p95) | <300ms | Application Insights | +| AI Service Response | <5s | Custom Monitoring | + +### Business Metrics +| Metric | Target | Measurement | +|--------|--------|-------------| +| User Registration | 100+/month | Analytics | +| Active Users | 50+/day | Analytics | +| Lesson Completion Rate | 80%+ | Database | +| Quiz Pass Rate | 80%+ | Database | +| User Retention (7-day) | 50%+ | Analytics | + +--- + +## 📝 Version History + +| Version | Date | Author | Changes | +|---------|------|--------|---------| +| 1.0 | May 31, 2025 | Vibe AI | Initial roadmap based on feature plans | + +--- + +## 📚 Related Documentation + +- [Feature Plans](features/README.md) - Detailed feature implementation plans +- [AGENTS.md](../AGENTS.md) - Development rules and workflows +- [Application Plan](architecture/application-plan.md) - Overall project vision +- [Backend Structure](architecture/backend-structure.md) - Technical architecture +- [Frontend Structure](architecture/frontend-structure.md) - Frontend architecture +- [Database Schema](database/initial-database-schema.sql) - Database design + +--- + +*Last updated: May 31, 2025* +*Roadmap based on Clean Architecture, TDD, and Agile principles* diff --git a/docs/architecture/application-plan.md b/docs/architecture/application-plan.md new file mode 100644 index 0000000..6d029c8 --- /dev/null +++ b/docs/architecture/application-plan.md @@ -0,0 +1,1541 @@ +# German Learning Web App - Comprehensive Plan + +--- + +## **Table of Contents** + +1. [Overview](#overview) +2. [Core Learning Experience](#core-learning-experience) +3. [Technical Stack](#technical-stack) +4. [Database Schema](#database-schema) +5. [Backend Architecture](#backend-architecture) +6. [Frontend Architecture](#frontend-architecture) +7. [AI Integration](#ai-integration) +8. [Speech Recognition and TTS](#speech-recognition-and-tts) +9. [Vocabulary Import](#vocabulary-import) +10. [TDD Approach](#tdd-approach) +11. [Development Roadmap](#development-roadmap) +12. [Example Workflows](#example-workflows) +13. [Deployment Notes](#deployment-notes) +14. [Open Questions](#open-questions) + +--- + +## **1. Overview** + +### **Purpose** + +A web application designed to teach German from **A1 to C1 levels** with a structured, immersive, and interactive approach. The app focuses on **vocabulary, grammar, reading, writing, speaking, and listening** through a **linear learning path** with lessons, quizzes, and a continuous story. + +### **Key Features** + +- **Fixed Learning Path**: Linear progression from A1 to C1 (only A1 implemented initially). +- **Lessons**: Each lesson includes vocabulary, grammar, story segments, and interactive exercises. +- **Quizzes**: Static difficulty, unlimited retakes, 80% passing score. +- **Story Integration**: AI-generated story (Mistral-Medium) tied to lessons, unlocking segment by segment. +- **Speech Recognition**: Self-hosted [Vosk](https://alphacephei.com/vosk/) for speaking exercises. +- **Text-to-Speech (TTS)**: Self-hosted [Coqui TTS](https://github.com/coqui-ai/TTS) for audio generation. +- **Gamification**: Points, badges, and streaks to motivate users. +- **Progress Tracking**: Dashboard with visual progress, quiz scores, and weak area analysis. +- **Vocabulary Import**: Scraped from [Goethe Institut](https://www.goethe.de/en/spr/ueb.html) and [DW Learn German](https://learngerman.dw.com/en/learn-german/s-9528). + +### **Target Audience** + +- Self-learners of German at all levels (A1–C1). +- Users who prefer structured, immersive, and interactive learning. + +--- + +## **2. Core Learning Experience** + +### **A. Learning Path** + +- **Levels**: A1 → A2 → B1 → B2 → C1 (only A1 implemented initially). +- **Progression**: Lessons **must be completed in order** within each level. +- **Unlocking**: Each lesson unlocks the next **only after passing its quiz** (80% score). +- **No Adaptive Difficulty**: Quizzes are static for all users. + +### **B. Lesson Structure** + +Each lesson follows this **immersive, audio-rich flow**: + + +| Step | Description | Audio/TTS Integration | +| ----------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| **1. Vocabulary** | 5–10 words (imported from Goethe/DW) with translations, articles (der/die/das), and images. | Coqui TTS generates audio for each word. | +| **2. Grammar** | Focus on one concept (e.g., nominative articles, present tense verbs). | Audio examples (e.g., "Der Mann **isst**"). | +| **3. Story** | AI-generated (Mistral-Medium) **daily life segment** using lesson vocabulary/grammar. | Coqui TTS generates audio for the story. | +| **4. Reading** | Short dialogue/text with comprehension questions. | Optional audio playback. | +| **5. Listening** | Play audio (e.g., a word, sentence, or dialogue) → user answers questions. | Vosk for user responses; Coqui TTS for prompts. | +| **6. Speaking** | User records themselves repeating phrases or answering prompts. | Vosk transcribes and validates responses. | +| **7. Writing** | Open-ended prompts (e.g., "Describe your morning using 3 new words."). | Mistral-Medium provides feedback. | +| **8. Quiz** | 10–15 questions: MCQ, fill-in-the-blank, **listening comprehension**, matching. | Coqui TTS for audio questions; Vosk for user. | + + +### **C. Story Integration** + +- **Linear Story**: One continuous narrative per level (e.g., A1: *"A Week in the Life of Anna"*). + - **Generated by Mistral-Medium** with prompts like: + ``` + Write a 2–3 paragraph story about daily life for a German A1 learner. + Use only A1 vocabulary and grammar (present tense, basic nouns/verbs). + Include the words: [aufstehen, frühstücken, Arbeit]. + Theme: "A typical morning." + ``` + - **Unlocked segment by segment** after completing lessons. + - **Audio**: Coqui TTS generates audio for each segment. + - **Interactive**: Click on words to see translations/definitions. + +### **D. Quizzes** + +- **Static Difficulty**: Same questions for all users. +- **Types**: + 1. **Multiple Choice**: "What is the article for 'Buch'? a) der b) die c) das" + 2. **Fill-in-the-Blank**: "Anna _____ um 7 Uhr auf." (steht) + 3. **Listening Comprehension**: + - Play audio (e.g., *"Das ist ein Buch."*) → user selects the correct translation or image. + - Audio generated via Coqui TTS. + 4. **Matching**: Drag words to their articles (der/die/das). +- **Passing**: 80% score to advance. +- **Retakes**: Unlimited, no penalties. + +### **E. Progress Tracking** + +- **User Dashboard**: + - Visual progress bar for current level (e.g., "A1: 3/10 lessons completed"). + - Story progress (e.g., "Chapter 2/5 unlocked"). + - Quiz scores history. + - Weak areas (e.g., "You often confuse 'die' and 'das'—try this exercise!"). +- **Database**: `UserProgress` table tracks completed lessons, quiz scores, and timestamps. + +--- + +## **3. Technical Stack** + + +| Component | Technology | Notes | +| ---------------------- | -------------------------------------------- | ----------------------------------------- | +| **Backend** | .NET 9.0 (ASP.NET Core) |   | +| **Frontend** | React (TypeScript) + Vite | Mobile-first with Tailwind CSS | +| **Database** | PostgreSQL |   | +| **Auth** | ASP.NET Core Identity + JWT |   | +| **AI Integration** | Mistral-Medium API | Story/lesson generation, feedback | +| **Speech Recognition** | [Vosk](https://alphacephei.com/vosk/) | Model: `vosk-model-de-0.22` (mid-range) | +| **TTS** | [Coqui TTS](https://github.com/coqui-ai/TTS) | Open-source, self-hosted, supports German | + + +--- + +## **4. Database Schema** + +### **SQL Script for PostgreSQL** + +```sql +-- Enable UUID extension for unique identifiers +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Users and Authentication +CREATE TABLE Users ( + Id SERIAL PRIMARY KEY, + Username VARCHAR(50) UNIQUE NOT NULL, + Email VARCHAR(100) UNIQUE NOT NULL, + PasswordHash VARCHAR(255) NOT NULL, + CurrentLevel VARCHAR(10) DEFAULT 'A1', + Streak INT DEFAULT 0, + TotalPoints INT DEFAULT 0, + CreatedAt TIMESTAMP DEFAULT NOW() +); + +-- User Progress Tracking +CREATE TABLE UserProgress ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + LessonId INT, + IsCompleted BOOLEAN DEFAULT FALSE, + QuizScore INT, + LastAttemptDate TIMESTAMP DEFAULT NOW(), + UNIQUE(UserId, LessonId) +); + +-- Learning Content: Levels +CREATE TABLE Levels ( + Id SERIAL PRIMARY KEY, + Name VARCHAR(10) UNIQUE NOT NULL, -- e.g., "A1", "A2" + Description TEXT, + Order INT UNIQUE NOT NULL +); + +-- Learning Content: Lessons +CREATE TABLE Lessons ( + Id SERIAL PRIMARY KEY, + LevelId INT REFERENCES Levels(Id) ON DELETE CASCADE, + Title VARCHAR(100) NOT NULL, + Order INT NOT NULL, + Topic VARCHAR(100) NOT NULL, + UNIQUE(LevelId, Order) +); + +-- Vocabulary +CREATE TABLE Vocabulary ( + Id SERIAL PRIMARY KEY, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + Word VARCHAR(50) NOT NULL, + Translation VARCHAR(100) NOT NULL, + Article VARCHAR(10) CHECK (Article IN ('der', 'die', 'das', '')), + AudioUrl VARCHAR(255), + ImageUrl VARCHAR(255), + Source VARCHAR(50) CHECK (Source IN ('Goethe', 'DW')) +); + +-- Story Segments +CREATE TABLE StorySegments ( + Id SERIAL PRIMARY KEY, + LevelId INT REFERENCES Levels(Id) ON DELETE CASCADE, + LessonId INT REFERENCES Lessons(Id) ON DELETE SET NULL, + Content TEXT NOT NULL, + AudioUrl VARCHAR(255), + Order INT NOT NULL, + UNIQUE(LevelId, Order) +); + +-- Quizzes +CREATE TABLE Quizzes ( + Id SERIAL PRIMARY KEY, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + PassingScore INT DEFAULT 80 +); + +-- Quiz Questions +CREATE TABLE Questions ( + Id SERIAL PRIMARY KEY, + QuizId INT REFERENCES Quizzes(Id) ON DELETE CASCADE, + Type VARCHAR(20) NOT NULL CHECK (Type IN ('mcq', 'fill_in', 'listening', 'matching')), + Content TEXT NOT NULL, + CorrectAnswer TEXT NOT NULL, + AudioUrl VARCHAR(255), + Options TEXT[], -- For MCQ: ['option1', 'option2'] + Points INT DEFAULT 1 +); + +-- Gamification: Badges +CREATE TABLE Badges ( + Id SERIAL PRIMARY KEY, + Name VARCHAR(50) NOT NULL, + Description TEXT, + ImageUrl VARCHAR(255), + UNIQUE(Name) +); + +-- User Badges +CREATE TABLE UserBadges ( + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + BadgeId INT REFERENCES Badges(Id) ON DELETE CASCADE, + EarnedDate TIMESTAMP DEFAULT NOW(), + PRIMARY KEY (UserId, BadgeId) +); + +-- Practice Sessions (Speaking/Writing) +CREATE TABLE PracticeSessions ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + Type VARCHAR(20) NOT NULL CHECK (Type IN ('speaking', 'writing')), + Prompt TEXT NOT NULL, + UserResponse TEXT, + Feedback TEXT, + Date TIMESTAMP DEFAULT NOW() +); + +-- Audio Files Metadata (Optional) +CREATE TABLE AudioFiles ( + Id SERIAL PRIMARY KEY, + Text TEXT NOT NULL, + FilePath VARCHAR(255) NOT NULL, + Type VARCHAR(20) NOT NULL CHECK (Type IN ('vocabulary', 'story', 'quiz')), + CreatedAt TIMESTAMP DEFAULT NOW(), + UNIQUE(Text, Type) +); + +-- Indexes for Performance +CREATE INDEX idx_userprogress_userid ON UserProgress(UserId); +CREATE INDEX idx_vocabulary_lessonid ON Vocabulary(LessonId); +CREATE INDEX idx_questions_quizid ON Questions(QuizId); +``` + +--- + +## **5. Backend Architecture** + +### **Project Structure** + +``` +GermanApp/ +├── Controllers/ +│ ├── AuthController.cs +│ ├── LessonsController.cs +│ ├── QuizzesController.cs +│ ├── StoryController.cs +│ ├── SpeechController.cs +│ ├── TtsController.cs +│ └── VocabularyController.cs +├── Services/ +│ ├── AuthService.cs +│ ├── LessonService.cs +│ ├── QuizService.cs +│ ├── StoryService.cs +│ ├── VoskService.cs +│ ├── TtsService.cs +│ ├── VocabularyImportService.cs +│ └── interfaces/ +│ ├── IVoskService.cs +│ ├── ITtsService.cs +│ └── ... +├── Models/ +│ ├── User.cs +│ ├── Lesson.cs +│ ├── Vocabulary.cs +│ ├── Quiz.cs +│ ├── StorySegment.cs +│ ├── DTOs/ +│ │ ├── LessonDto.cs +│ │ ├── QuizDto.cs +│ │ └── ... +│ └── AppDbContext.cs +├── Data/ +│ └── AppDbContext.cs +├── Tests/ +│ ├── Unit/ +│ │ ├── Services/ +│ │ │ ├── VoskServiceTests.cs +│ │ │ ├── TtsServiceTests.cs +│ │ │ └── ... +│ │ └── Controllers/ +│ │ ├── LessonsControllerTests.cs +│ │ └── ... +│ └── Integration/ +│ ├── LessonsControllerIntegrationTests.cs +│ └── ... +├── appsettings.json +├── Program.cs +└── GermanApp.csproj +``` + +### **Key Services** + +#### **1. `IVoskService` and `VoskService**` + +```csharp +public interface IVoskService +{ + Task RecognizeSpeechAsync(byte[] audioData); +} + +public class VoskService : IVoskService +{ + private readonly string _modelPath; + + public VoskService(IConfiguration configuration) + { + _modelPath = configuration["Vosk:ModelPath"]; + } + + public async Task RecognizeSpeechAsync(byte[] audioData) + { + // Use Vosk .NET wrapper or CLI + // Example using CLI (simplified) + var tempFile = Path.GetTempFileName(); + await File.WriteAllBytesAsync(tempFile, audioData); + + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "python", + Arguments = $"-m vosk.transcribe --model {_modelPath} --input {tempFile}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + process.Start(); + var result = await process.StandardOutput.ReadToEndAsync(); + await process.WaitForExitAsync(); + + return result.Trim(); + } +} +``` + +#### **2. `ITtsService` and `TtsService**` + +```csharp +public interface ITtsService +{ + Task GenerateTtsAsync(string text); +} + +public class TtsService : ITtsService +{ + private readonly string _pythonPath; + private readonly string _coquiModelName; + + public TtsService(IConfiguration configuration) + { + _pythonPath = configuration["Coqui:PythonPath"]; + _coquiModelName = configuration["Coqui:ModelName"]; + } + + public async Task GenerateTtsAsync(string text) + { + var tempOutputFile = Path.GetTempFileName() + ".wav"; + + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = _pythonPath, + Arguments = $"-m TTS.api --model {_coquiModelName} --text \"{text}\" --out_path {tempOutputFile}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + process.Start(); + await process.WaitForExitAsync(); + + return await File.ReadAllBytesAsync(tempOutputFile); + } +} +``` + +#### **3. `IStoryService` and `StoryService**` + +```csharp +public interface IStoryService +{ + Task GenerateStorySegmentAsync(List vocabulary, string theme, string level); +} + +public class StoryService : IStoryService +{ + private readonly HttpClient _httpClient; + private readonly string _mistralApiKey; + + public StoryService(HttpClient httpClient, IConfiguration configuration) + { + _httpClient = httpClient; + _mistralApiKey = configuration["Mistral:ApiKey"]; + } + + public async Task GenerateStorySegmentAsync(List vocabulary, string theme, string level) + { + var prompt = $@" + Write a 2–3 paragraph story about daily life for a German {level} learner. + Use only {level} vocabulary and grammar (present tense, basic nouns/verbs). + Include the following words: {string.Join(", ", vocabulary)}. + Theme: "{theme}". + "; + + var request = new + { + model = "mistral-medium", + messages = new[] { new { role = "user", content = prompt } } + }; + + _httpClient.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _mistralApiKey); + + var response = await _httpClient.PostAsJsonAsync("https://api.mistral.ai/v1/chat/completions", request); + var result = await response.Content.ReadFromJsonAsync(); + + return result.Choices[0].Message.Content; + } +} + +public class MistralResponse +{ + public Choice[] Choices { get; set; } +} + +public class Choice +{ + public Message Message { get; set; } +} + +public class Message +{ + public string Content { get; set; } +} +``` + +#### **4. `IQuizService` and `QuizService**` + +```csharp +public interface IQuizService +{ + Task SubmitQuizAsync(int userId, int quizId, List answers); +} + +public class QuizService : IQuizService +{ + private readonly AppDbContext _dbContext; + + public QuizService(AppDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task SubmitQuizAsync(int userId, int quizId, List answers) + { + var quiz = await _dbContext.Quizzes + .Include(q => q.Questions) + .FirstOrDefaultAsync(q => q.Id == quizId); + + if (quiz == null) + throw new NotFoundException("Quiz not found."); + + var score = 0; + var totalPoints = quiz.Questions.Sum(q => q.Points); + + foreach (var answer in answers) + { + var question = quiz.Questions.FirstOrDefault(q => q.Id == answer.QuestionId); + if (question != null && question.CorrectAnswer == answer.UserAnswer) + { + score += question.Points; + } + } + + var passed = (score * 100 / totalPoints) >= quiz.PassingScore; + + // Update user progress + var userProgress = await _dbContext.UserProgress + .FirstOrDefaultAsync(up => up.UserId == userId && up.LessonId == quiz.LessonId); + + if (userProgress == null) + { + userProgress = new UserProgress + { + UserId = userId, + LessonId = quiz.LessonId, + IsCompleted = passed, + QuizScore = score, + LastAttemptDate = DateTime.UtcNow + }; + _dbContext.UserProgress.Add(userProgress); + } + else + { + userProgress.IsCompleted = passed; + userProgress.QuizScore = score; + userProgress.LastAttemptDate = DateTime.UtcNow; + } + + await _dbContext.SaveChangesAsync(); + + return new QuizResult + { + Passed = passed, + Score = score, + TotalPoints = totalPoints + }; + } +} + +public class QuizResult +{ + public bool Passed { get; set; } + public int Score { get; set; } + public int TotalPoints { get; set; } +} + +public class AnswerDto +{ + public int QuestionId { get; set; } + public string UserAnswer { get; set; } +} +``` + +### **API Endpoints** + + +| Endpoint | Method | Description | Request Body | Response | +| ------------------------------- | ------ | ------------------------------- | ------------------------------------------- | -------------------------------------------- | +| `/api/auth/register` | POST | Register a new user. | `{ username, email, password }` | `{ userId, token }` | +| `/api/auth/login` | POST | Log in a user. | `{ email, password }` | `{ userId, token }` | +| `/api/auth/me` | GET | Get current user details. | - | `{ user }` | +| `/api/levels` | GET | List all levels. | - | `[{ level }]` | +| `/api/levels/{levelId}/lessons` | GET | List lessons for a level. | - | `[{ lesson }]` | +| `/api/lessons/{id}` | GET | Get lesson details. | - | `{ lesson, vocabulary, storySegment, quiz }` | +| `/api/quizzes/{quizId}` | GET | Get quiz questions. | - | `{ quiz, questions }` | +| `/api/quizzes/{quizId}/submit` | POST | Submit quiz answers. | `{ answers: [{ questionId, userAnswer }] }` | `{ passed, score, totalPoints }` | +| `/api/story/{levelId}/segments` | GET | Get story segments for a level. | - | `[{ storySegment }]` | +| `/api/speech/recognize` | POST | Recognize speech. | `{ audio: File }` | `{ text }` | +| `/api/tts/generate` | POST | Generate TTS audio. | `{ text }` | `{ audio: File }` | +| `/api/ai/generate-story` | POST | Generate a story segment. | `{ vocabulary, theme, level }` | `{ storyText }` | +| `/api/ai/feedback` | POST | Get feedback for writing. | `{ userInput, level }` | `{ corrected, explanation, encouragement }` | +| `/api/vocabulary/import` | POST | Import vocabulary (admin). | - | `{ success }` | + + +--- + +## **6. Frontend Architecture** + +### **Project Structure** + +``` +german-app-frontend/ +├── public/ +│ └── audio/ -- Static audio files (vocabulary, story, quizzes) +├── src/ +│ ├── components/ +│ │ ├── Dashboard/ +│ │ │ ├── ProgressBar.tsx +│ │ │ ├── NextLessonButton.tsx +│ │ │ └── StoryProgress.tsx +│ │ ├── Lesson/ +│ │ │ ├── VocabularyTab.tsx +│ │ │ ├── GrammarTab.tsx +│ │ │ ├── StoryTab.tsx +│ │ │ ├── ListeningExercise.tsx +│ │ │ ├── SpeakingExercise.tsx +│ │ │ ├── WritingExercise.tsx +│ │ │ └── QuizTab.tsx +│ │ ├── Quiz/ +│ │ │ ├── McqQuestion.tsx +│ │ │ ├── FillInBlank.tsx +│ │ │ ├── ListeningQuestion.tsx +│ │ │ └── QuizResults.tsx +│ │ ├── Story/ +│ │ │ ├── StorySegment.tsx +│ │ │ └── StoryPlayer.tsx +│ │ ├── Shared/ +│ │ │ ├── AudioPlayer.tsx +│ │ │ ├── Recorder.tsx +│ │ │ ├── Button.tsx +│ │ │ └── types.ts +│ │ └── Layout/ +│ │ ├── Header.tsx +│ │ └── Footer.tsx +│ ├── pages/ +│ │ ├── DashboardPage.tsx +│ │ ├── LessonPage.tsx +│ │ ├── QuizPage.tsx +│ │ ├── StoryPage.tsx +│ │ ├── PracticePage.tsx +│ │ └── ProfilePage.tsx +│ ├── hooks/ +│ │ ├── useAudio.ts +│ │ ├── useRecorder.ts +│ │ ├── useApi.ts +│ │ └── useAuth.ts +│ ├── services/ +│ │ ├── api.ts +│ │ ├── authService.ts +│ │ ├── lessonService.ts +│ │ ├── quizService.ts +│ │ ├── speechService.ts +│ │ └── ttsService.ts +│ ├── styles/ +│ │ ├── tailwind.css +│ │ └── global.css +│ ├── utils/ +│ │ ├── constants.ts +│ │ └── helpers.ts +│ ├── types/ +│ │ └── index.ts +│ ├── App.tsx +│ └── main.tsx +├── package.json +├── vite.config.ts +└── tsconfig.json +``` + +### **Key Components** + +#### **1. `AudioPlayer.tsx**` + +```tsx +import React, { useRef } from 'react'; + +interface AudioPlayerProps { + audioUrl: string; + className?: string; +} + +export const AudioPlayer: React.FC = ({ audioUrl, className }) => { + const audioRef = useRef(null); + + const handlePlay = () => { + audioRef.current?.play(); + }; + + return ( +
+
+ ); +}; +``` + +#### **2. `Recorder.tsx**` + +```tsx +import React, { useState, useRef } from 'react'; + +interface RecorderProps { + onTranscript: (text: string) => void; + className?: string; +} + +export const Recorder: React.FC = ({ onTranscript, className }) => { + const [isRecording, setIsRecording] = useState(false); + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + + const startRecording = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/wav' }); + const formData = new FormData(); + formData.append('audio', audioBlob, 'recording.wav'); + + const response = await fetch('/api/speech/recognize', { + method: 'POST', + body: formData, + }); + const { text } = await response.json(); + onTranscript(text); + }; + + mediaRecorderRef.current.start(); + setIsRecording(true); + } catch (error) { + console.error('Error accessing microphone:', error); + } + }; + + const stopRecording = () => { + if (mediaRecorderRef.current) { + mediaRecorderRef.current.stop(); + setIsRecording(false); + } + }; + + return ( +
+ +
+ ); +}; +``` + +#### **3. `ListeningQuestion.tsx**` + +```tsx +import React, { useRef, useState } from 'react'; +import { AudioPlayer } from '../Shared/AudioPlayer'; + +interface ListeningQuestionProps { + audioUrl: string; + options: string[]; + correctAnswer: string; + onAnswer: (isCorrect: boolean) => void; + className?: string; +} + +export const ListeningQuestion: React.FC = ({ + audioUrl, + options, + correctAnswer, + onAnswer, + className +}) => { + const [selected, setSelected] = useState(null); + + const handleSelect = (option: string) => { + setSelected(option); + onAnswer(option === correctAnswer); + }; + + return ( +
+ +
+ {options.map((option) => ( + + ))} +
+
+ ); +}; +``` + +#### **4. `LessonPage.tsx**` + +```tsx +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { VocabularyTab } from './VocabularyTab'; +import { GrammarTab } from './GrammarTab'; +import { StoryTab } from './StoryTab'; +import { QuizTab } from '../Quiz/QuizTab'; + +interface Lesson { + id: number; + title: string; + vocabulary: Vocabulary[]; + grammar: Grammar; + storySegment: StorySegment; + quizId: number; +} + +interface Vocabulary { + id: number; + word: string; + translation: string; + article: string; + audioUrl: string; +} + +interface Grammar { + topic: string; + explanation: string; + examples: string[]; +} + +interface StorySegment { + id: number; + content: string; + audioUrl: string; +} + +export const LessonPage: React.FC = () => { + const { lessonId } = useParams<{ lessonId: string }>(); + const [lesson, setLesson] = useState(null); + const [activeTab, setActiveTab] = useState<'vocabulary' | 'grammar' | 'story' | 'quiz'>('vocabulary'); + + useEffect(() => { + const fetchLesson = async () => { + const response = await fetch(`/api/lessons/${lessonId}`); + const data = await response.json(); + setLesson(data); + }; + fetchLesson(); + }, [lessonId]); + + if (!lesson) return
Loading...
; + + return ( +
+

{lesson.title}

+ +
+ + + + +
+ +
+ {activeTab === 'vocabulary' && ( + + )} + {activeTab === 'grammar' && ( + + )} + {activeTab === 'story' && ( + + )} + {activeTab === 'quiz' && ( + + )} +
+
+ ); +}; +``` + +--- + +## **7. AI Integration** + +### **Mistral-Medium API** + +- **Base URL**: `https://api.mistral.ai/v1/` +- **Authentication**: Bearer token (`MISTRAL_API_KEY`). + +#### **Prompts** + +##### **1. Story Generation** + +``` +Write a 2–3 paragraph story about daily life for a German {level} learner. +Use only {level} vocabulary and grammar (present tense, basic nouns/verbs like 'gehen', 'einkaufen', 'essen'). +Include the following words: {vocabularyList}. +Theme: "{theme}". + +Example for A1: +- Vocabulary: ["aufstehen", "frühstücken", "Arbeit", "schlafen"] +- Theme: "A typical morning" +``` + +**Output Example**: + +``` +Anna steht jeden Morgen um 7 Uhr auf. Sie geht ins Badezimmer und wäscht sich das Gesicht. +Dann frühstückt sie in der Küche. Sie isst ein Brot und trinkt einen Kaffee. +Um 8 Uhr geht Anna zur Arbeit. Sie arbeitet bis 17 Uhr. Am Abend kocht sie Abendessen und schläft um 22 Uhr. +``` + +##### **2. Writing Feedback** + +``` +The user wrote the following German sentence: "{userInput}". +They are an {level} learner. Provide: +1. A corrected version of the sentence. +2. A brief explanation of mistakes (in English). +3. Encouragement (e.g., "Good try! Remember that nouns are always capitalized."). + +Example for A1: +- User Input: "ich stehe um 7 uhr auf." +``` + +**Output Example**: + +```json +{ + "corrected": "Ich stehe um 7 Uhr auf.", + "explanation": "'Ich' and 'Uhr' must be capitalized in German. Also, '7 Uhr' should be written as '7 Uhr' (no space).", + "encouragement": "Good try! Remember that all nouns and the word 'Ich' are always capitalized in German." +} +``` + +--- + +## **8. Speech Recognition and TTS** + +### **A. Vosk Speech Recognition** + +- **Model**: `vosk-model-de-0.22` (mid-range, ~500MB). +- **Download**: [Vosk Models](https://alphacephei.com/vosk/models) +- **Integration**: + - **Backend**: Use the [Vosk .NET wrapper](https://github.com/alphacep/vosk-api) or call Vosk via Python CLI. + - **Frontend**: Record audio using the `Recorder` component and send it to `/api/speech/recognize`. + +#### **Example Workflow** + +1. User clicks "Record" in the `SpeakingExercise` component. +2. Frontend records audio and sends it to `/api/speech/recognize`. +3. Backend uses Vosk to transcribe the audio. +4. Backend returns the transcribed text (e.g., "Ich stehe um 8 Uhr auf."). +5. Frontend compares the transcription to the expected answer and provides feedback. + +### **B. Coqui TTS** + +- **Model**: `tts_models/de/deu/fairseq/vits` (high-quality German TTS). +- **Installation**: + ```bash + pip install TTS + python -m TTS --download_model tts_models/de/deu/fairseq/vits + ``` +- **Integration**: + - **Backend**: Call Coqui TTS via Python CLI to generate audio for vocabulary, story segments, and quiz questions. + - **Frontend**: Play generated audio using the `AudioPlayer` component. + +#### **Example Workflow** + +1. Backend calls Coqui TTS to generate audio for a vocabulary word (e.g., "aufstehen"). +2. Backend saves the audio file to `/public/audio/vocabulary/{id}.wav`. +3. Frontend plays the audio when the user clicks the "Play" button. + +--- + +## **9. Vocabulary Import** + +### **Sources** + +1. [Goethe Institut A1 Word List](https://www.goethe.de/en/spr/ueb.html) +2. [DW Learn German A1 Vocabulary](https://learngerman.dw.com/en/learn-german/s-9528) + +### **Implementation** + +1. **Scraping Script**: + - Use a .NET HTTP client (e.g., `HttpClient`) to fetch and parse vocabulary from Goethe/DW. + - Store words in the `Vocabulary` table with their translations, articles, and source. +2. **Audio Generation**: + - For each word, call Coqui TTS to generate audio and save the `AudioUrl`. +3. **Admin Endpoint**: + - `POST /api/vocabulary/import` (admin-only). + +#### **Example Scraping Logic (Pseudocode)** + +```csharp +public class VocabularyImportService : IVocabularyImportService +{ + private readonly ITtsService _ttsService; + private readonly AppDbContext _dbContext; + private readonly HttpClient _httpClient; + + public async Task ImportVocabularyAsync() + { + var goetheWords = await ScrapeGoetheA1Words(); + var dwWords = await ScrapeDwA1Words(); + + foreach (var word in goetheWords.Concat(dwWords)) + { + var audioBytes = await _ttsService.GenerateTtsAsync(word.German); + var audioUrl = $"/audio/vocabulary/{Guid.NewGuid()}.wav"; + await File.WriteAllBytesAsync(audioUrl, audioBytes); + + _dbContext.Vocabulary.Add(new Vocabulary + { + Word = word.German, + Translation = word.English, + Article = word.Article, + AudioUrl = audioUrl, + Source = word.Source + }); + } + await _dbContext.SaveChangesAsync(); + } + + private async Task> ScrapeGoetheA1Words() { /* ... */ } + private async Task> ScrapeDwA1Words() { /* ... */ } +} + +public class Word +{ + public string German { get; set; } + public string English { get; set; } + public string Article { get; set; } + public string Source { get; set; } // "Goethe" or "DW" +} +``` + +--- + +## **10. TDD Approach** + +### **Backend Tests (.NET)** + +- **Framework**: xUnit or NUnit. +- **Mocking**: Moq for mocking dependencies (e.g., Vosk, Coqui TTS, Mistral-Medium). + +#### **Example Tests** + +##### **1. VoskService Tests** + +```csharp +using Moq; +using Xunit; + +public class VoskServiceTests +{ + [Fact] + public async Task RecognizeSpeech_ReturnsCorrectTranscription() + { + // Arrange + var mockVoskWrapper = new Mock(); + mockVoskWrapper.Setup(w => w.Recognize(It.IsAny())) + .Returns("Guten Morgen"); + var service = new VoskService(mockVoskWrapper.Object); + + // Act + var result = await service.RecognizeSpeechAsync(new byte[100]); + + // Assert + Assert.Equal("Guten Morgen", result); + } + + [Fact] + public async Task RecognizeSpeech_HandlesEmptyAudio() + { + // Arrange + var mockVoskWrapper = new Mock(); + mockVoskWrapper.Setup(w => w.Recognize(It.IsAny())) + .Throws(); + var service = new VoskService(mockVoskWrapper.Object); + + // Act & Assert + await Assert.ThrowsAsync(() => service.RecognizeSpeechAsync(new byte[0])); + } +} +``` + +##### **2. TtsService Tests** + +```csharp +public class TtsServiceTests +{ + [Fact] + public async Task GenerateTts_ReturnsAudioFile() + { + // Arrange + var mockCoquiWrapper = new Mock(); + mockCoquiWrapper.Setup(w => w.GenerateTts(It.IsAny())) + .Returns(new byte[100]); // Dummy audio data + var service = new TtsService(mockCoquiWrapper.Object); + + // Act + var result = await service.GenerateTtsAsync("Guten Morgen"); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result); + } +} +``` + +##### **3. QuizService Tests** + +```csharp +public class QuizServiceTests +{ + [Fact] + public async Task SubmitQuiz_PassingScore_UpdatesProgress() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: "TestDb") + .Options; + var dbContext = new AppDbContext(options); + + var user = new User { Id = 1, CurrentLevel = "A1" }; + var lesson = new Lesson { Id = 1, LevelId = 1 }; + var quiz = new Quiz { Id = 1, LessonId = 1, PassingScore = 80 }; + var question = new Question { Id = 1, QuizId = 1, CorrectAnswer = "der", Points = 1 }; + + dbContext.Users.Add(user); + dbContext.Lessons.Add(lesson); + dbContext.Quizzes.Add(quiz); + dbContext.Questions.Add(question); + await dbContext.SaveChangesAsync(); + + var service = new QuizService(dbContext); + var answers = new List { new AnswerDto { QuestionId = 1, UserAnswer = "der" } }; + + // Act + var result = await service.SubmitQuizAsync(1, 1, answers); + + // Assert + Assert.True(result.Passed); + Assert.Equal(1, result.Score); + + var userProgress = await dbContext.UserProgress.FirstOrDefaultAsync(); + Assert.NotNull(userProgress); + Assert.True(userProgress.IsCompleted); + } +} +``` + +### **Frontend Tests (React)** + +- **Framework**: Jest + React Testing Library. +- **Mocking**: Mock API calls and browser APIs (e.g., `navigator.mediaDevices`). + +#### **Example Tests** + +##### **1. AudioPlayer Tests** + +```tsx +import { render, screen } from '@testing-library/react'; +import { AudioPlayer } from './AudioPlayer'; + +test('renders audio player with correct src', () => { + render(); + const audioElement = screen.getByTestId('audio-player'); + expect(audioElement).toHaveAttribute('src', '/audio/test.wav'); +}); +``` + +##### **2. Recorder Tests** + +```tsx +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { Recorder } from './Recorder'; + +test('calls onTranscript with recognized text', async () => { + const mockOnTranscript = jest.fn(); + const mockMediaDevices = { + getUserMedia: jest.fn().mockResolvedValue({ + getTracks: () => [], + stop: jest.fn(), + }), + }; + Object.assign(navigator, { mediaDevices: mockMediaDevices }); + + render(); + fireEvent.click(screen.getByText('🎤 Start Recording')); + + // Mock fetch for /api/speech/recognize + global.fetch = jest.fn().mockResolvedValue({ + json: () => Promise.resolve({ text: "Guten Morgen" }), + }); + + // Simulate stopping recording + await waitFor(() => { + expect(mockOnTranscript).toHaveBeenCalledWith("Guten Morgen"); + }); +}); +``` + +##### **3. ListeningQuestion Tests** + +```tsx +test('selects correct answer for listening question', async () => { + const mockOnAnswer = jest.fn(); + render( + + ); + + fireEvent.click(screen.getByText('▶️ Play')); + fireEvent.click(screen.getByText('A')); + + await waitFor(() => { + expect(mockOnAnswer).toHaveBeenCalledWith(true); + }); +}); +``` + +--- + +## **11. Development Roadmap** + +### **Phase 1: MVP (A1 Only)** + + +| Task | Description | Priority | Estimated Time | +| ---- | -------------------------------------------------- | -------- | -------------- | +| 1.1 | Set up .NET 9.0 backend project | High | 2–4 hours | +| 1.2 | Set up PostgreSQL database | High | 1–2 hours | +| 1.3 | Implement Auth (JWT) | High | 4–6 hours | +| 1.4 | Design and implement database schema | High | 2–3 hours | +| 1.5 | Integrate Vosk for speech recognition | High | 4–8 hours | +| 1.6 | Integrate Coqui TTS for audio generation | High | 4–8 hours | +| 1.7 | Implement Lesson/Quiz/Story APIs | High | 6–10 hours | +| 1.8 | Set up React + TypeScript frontend | High | 2–4 hours | +| 1.9 | Implement Dashboard, Lesson, Quiz, and Story pages | High | 8–12 hours | +| 1.10 | Import vocabulary from Goethe/DW | High | 4–6 hours | +| 1.11 | Generate audio for vocabulary and story segments | High | 4–8 hours | +| 1.12 | Implement Mistral-Medium for story generation | Medium | 2–4 hours | +| 1.13 | Add listening comprehension to quizzes | Medium | 2–4 hours | +| 1.14 | Write unit tests for backend and frontend | Medium | 6–10 hours | + + +### **Phase 2: Polish** + + +| Task | Description | Priority | Estimated Time | +| ---- | ----------------------------------------------- | -------- | -------------- | +| 2.1 | Add gamification (points, badges, streaks) | Medium | 4–6 hours | +| 2.2 | Optimize for mobile devices | Medium | 4–8 hours | +| 2.3 | Add progress charts and analytics | Medium | 4–6 hours | +| 2.4 | Improve UI/UX (e.g., animations, accessibility) | Low | 4–8 hours | + + +### **Phase 3: Scaling** + + +| Task | Description | Priority | Estimated Time | +| ---- | -------------------------------------------------------- | -------- | -------------- | +| 3.1 | Add A2 level | Low | 8–12 hours | +| 3.2 | Add more interactive exercises (e.g., dialogue practice) | Low | 6–10 hours | +| 3.3 | Optimize performance (e.g., caching, lazy loading) | Low | 4–8 hours | + + +--- + +## **12. Example Workflows** + +### **A. User Completes a Lesson** + +1. **Dashboard**: + - User logs in and sees their dashboard with progress for A1. + - Clicks "Start Lesson 1: Daily Routine". +2. **Vocabulary Tab**: + - Sees 5 words: *aufstehen, frühstücken, Arbeit, schlafen, danke*. + - Each word has German, English, article, and an **audio button** (Coqui TTS). +3. **Grammar Tab**: + - Reads about present tense verbs (e.g., *ich stehe auf, du isst*). + - Listens to **audio examples** (Coqui TTS). +4. **Story Tab**: + - Reads: *"Anna steht um 7 Uhr auf. Sie frühstückt und geht zur Arbeit."* + - Listens to **audio** (Coqui TTS). + - Clicks on *aufstehen* to see the translation: "to get up". +5. **Practice Tab**: + - **Listening**: Plays audio of *"Anna schläft um 22 Uhr."* → selects the correct translation. + - **Speaking**: Records themselves saying *"Ich stehe um 8 Uhr auf."* → Vosk transcribes and validates. + - **Writing**: Types a sentence about their morning → Mistral-Medium provides feedback. +6. **Quiz Tab**: + - Answers 10 questions (MCQ, fill-in, listening). + - Scores 90% → lesson marked as completed; next lesson unlocked. + +### **B. Admin Imports Vocabulary** + +1. Admin calls `POST /api/vocabulary/import`. +2. Backend scrapes Goethe/DW and imports words into `Vocabulary` table. +3. For each word, Coqui TTS generates audio and stores the `AudioUrl`. + +### **C. Story Generation** + +1. Admin triggers `POST /api/ai/generate-story` for A1 Lesson 1. +2. Backend calls Mistral-Medium with the prompt: + ``` + Write a 2–3 paragraph story about daily life for a German A1 learner. + Use only A1 vocabulary and grammar. Include the words: ["aufstehen", "frühstücken"]. + Theme: "A typical morning." + ``` +3. Mistral-Medium returns the story text. +4. Backend stores the story in `StorySegments` and generates audio using Coqui TTS. + +--- + +## **13. Deployment Notes** + +### **Self-Hosting Requirements** + +1. **Server**: + - Linux/Windows server with Docker (recommended) or direct installation. + - Minimum **4GB RAM** (8GB recommended for Vosk/Coqui TTS). + - **Storage**: ~1GB for Vosk/Coqui models + audio files. +2. **Backend**: + - .NET 9.0 runtime. + - PostgreSQL database. + - Environment variables: + ```bash + export ConnectionStrings__Default="Host=localhost;Database=germandb;Username=postgres;Password=yourpassword" + export Vosk__ModelPath="/models/vosk-model-de-0.22" + export Coqui__PythonPath="/usr/bin/python3" + export Coqui__ModelName="tts_models/de/deu/fairseq/vits" + export Mistral__ApiKey="your-mistral-api-key" + ``` +3. **Frontend**: + - Node.js (v18+) for Vite/React. +4. **Vosk**: + - Download `vosk-model-de-0.22` and place it in `/models/vosk`. + - Ensure the .NET app has read access to this directory. + - **Download Link**: [Vosk Models](https://alphacephei.com/vosk/models) +5. **Coqui TTS**: + - Install in a Python virtual environment: +6. **Mistral-Medium**: + - Sign up for an API key at [Mistral AI](https://mistral.ai/). + - Store the key in environment variables (as shown above). + +### **Docker Setup (Optional)** + +#### **1. Backend Dockerfile** + +```dockerfile +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src +COPY . . +RUN dotnet publish -c Release -o /app + +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /app +COPY --from=build /app . +COPY /models/vosk-model-de-0.22 /models/vosk-model-de-0.22 +ENV Vosk__ModelPath=/models/vosk-model-de-0.22 +ENTRYPOINT ["dotnet", "GermanApp.dll"] +``` + +#### **2. Frontend Dockerfile** + +```dockerfile +FROM node:18 +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build +EXPOSE 3000 +CMD ["npm", "run", "dev"] +``` + +#### **3. PostgreSQL Docker Compose** + +```yaml +version: '3' +services: + postgres: + image: postgres:15 + environment: + POSTGRES_PASSWORD: yourpassword + POSTGRES_DB: germandb + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "5000:80" + environment: + - ConnectionStrings__Default=Host=postgres;Database=germandb;Username=postgres;Password=yourpassword + - Vosk__ModelPath=/models/vosk-model-de-0.22 + - Coqui__PythonPath=/usr/bin/python3 + - Coqui__ModelName=tts_models/de/deu/fairseq/vits + - Mistral__ApiKey=${MISTRAL_API_KEY} + depends_on: + - postgres + volumes: + - ./models:/models + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "3000:3000" + depends_on: + - backend + +volumes: + postgres_data: +``` + +#### **4. Run the Stack** + +```bash +# Create a .env file in the backend directory +echo "MISTRAL_API_KEY=your-api-key" > backend/.env + +# Build and run the containers +docker-compose up --build +``` + +--- + +## **14. Open Questions** + +1. **Coqui TTS Model**: Should we default to `tts_models/de/deu/fairseq/vits` (higher quality, ~1.5GB) or start with a smaller model (e.g., `tts_models/de/deu/tacotron2-DDC`) for faster generation? +2. **Audio Storage**: Should audio files be stored in: + - The filesystem (e.g., `/var/www/audio`)? + - A dedicated `AudioFiles` table with file paths? + - A CDN (e.g., AWS S3) for scalability? +3. **Vosk Confidence Threshold**: Should we reject transcriptions with <80% confidence for speaking exercises? +4. **Story Audio**: Should story segments have **both text and audio**, or just audio? +5. **Quiz Feedback**: Should users receive **immediate feedback** after each quiz question, or only at the end? +6. **Mobile App**: Should we consider wrapping the web app in a **mobile app** (e.g., using Capacitor) for better mobile UX? + +--- + +## **Appendix: Glossary** + + +| Term | Definition | +| ------------------ | -------------------------------------------------------------------------- | +| **A1–C1** | Levels of the Common European Framework of Reference for Languages (CEFR). | +| **Vosk** | Open-source offline speech recognition toolkit. | +| **Coqui TTS** | Open-source text-to-speech library. | +| **Mistral-Medium** | Large language model by Mistral AI for text generation. | +| **TDD** | Test-Driven Development: Write tests before writing code. | +| **JWT** | JSON Web Token: Used for authentication. | +| **MCQ** | Multiple Choice Question. | +| **CEFR** | Common European Framework of Reference for Languages. | + + +--- + +## **Appendix: Sample Data** + +### **A. Sample Vocabulary (A1)** + + +| Id | Word | Translation | Article | AudioUrl | Source | +| --- | ----------- | ---------------- | ------- | ----------------------- | ------ | +| 1 | aufstehen | to get up | - | /audio/vocabulary/1.wav | Goethe | +| 2 | frühstücken | to eat breakfast | - | /audio/vocabulary/2.wav | Goethe | +| 3 | Arbeit | work | die | /audio/vocabulary/3.wav | DW | +| 4 | Buch | book | das | /audio/vocabulary/4.wav | Goethe | +| 5 | schlafen | to sleep | - | /audio/vocabulary/5.wav | DW | + + +### **B. Sample Story Segment (A1)** + +- **Id**: 1 +- **LevelId**: 1 (A1) +- **LessonId**: 1 +- **Content**: + ``` + Anna steht jeden Morgen um 7 Uhr auf. Sie geht ins Badezimmer und wäscht sich das Gesicht. + Dann frühstückt sie in der Küche. Sie isst ein Brot und trinkt einen Kaffee. + Um 8 Uhr geht Anna zur Arbeit. Sie arbeitet bis 17 Uhr. Am Abend kocht sie Abendessen und schläft um 22 Uhr. + ``` +- **AudioUrl**: `/audio/story/1.wav` +- **Order**: 1 + +### **C. Sample Quiz Questions (A1)** + + +| Id | QuizId | Type | Content | CorrectAnswer | AudioUrl | Options | +| --- | ------ | --------- | ----------------------------------- | --------------- | ----------------- | ------------------------------------- | +| 1 | 1 | mcq | What is the article for "Buch"? | das | - | ["der", "die", "das"] | +| 2 | 1 | fill_in | Anna _____ um 7 Uhr auf. | steht | - | - | +| 3 | 1 | listening | (Audio: "Das ist ein Buch.") | This is a book. | /audio/quiz/1.wav | ["This is a book.", "This is a cat."] | +| 4 | 1 | matching | Match the word to its article: Buch | das | - | ["der", "die", "das"] | + + +--- + +--- + +*Last updated: May 30, 2026* +*Author: Lasse Hansen (with assistance from Vibe, an AI by Mistral AI)* \ No newline at end of file diff --git a/docs/architecture/backend-structure.md b/docs/architecture/backend-structure.md new file mode 100644 index 0000000..d05397f --- /dev/null +++ b/docs/architecture/backend-structure.md @@ -0,0 +1,61 @@ +GermanApp/ +├── Controllers/ +│ ├── AuthController.cs -- User registration/login +│ ├── LessonsController.cs -- Lesson CRUD and retrieval +│ ├── QuizzesController.cs -- Quiz retrieval and submission +│ ├── StoryController.cs -- Story segment retrieval +│ ├── SpeechController.cs -- Vosk speech recognition +│ ├── TtsController.cs -- Coqui TTS audio generation +│ ├── VocabularyController.cs -- Vocabulary retrieval +│ └── UserProgressController.cs -- User progress tracking +│ +├── Services/ +│ ├── AuthService.cs +│ ├── LessonService.cs +│ ├── QuizService.cs +│ ├── StoryService.cs +│ ├── VoskService.cs -- Vosk integration +│ ├── TtsService.cs -- Coqui TTS integration +│ ├── MistralService.cs -- Mistral-Medium API calls +│ ├── VocabularyService.cs +│ └── UserProgressService.cs +│ +├── Models/ +│ ├── DTOs/ -- Data Transfer Objects +│ │ ├── LessonDto.cs +│ │ ├── QuizDto.cs +│ │ ├── StorySegmentDto.cs +│ │ ├── VocabularyDto.cs +│ │ └── UserProgressDto.cs +│ │ +│ ├── Entities/ -- Database entities (mirroring schema) +│ │ ├── User.cs +│ │ ├── Lesson.cs +│ │ ├── Vocabulary.cs +│ │ ├── WordMetadata.cs +│ │ ├── WordForm.cs +│ │ ├── StorySegment.cs +│ │ ├── Quiz.cs +│ │ ├── Question.cs +│ │ ├── QuestionOption.cs +│ │ ├── UserProgress.cs +│ │ ├── QuizAttempt.cs +│ │ └── ... +│ │ +│ └── Requests/ -- API request models +│ ├── SubmitQuizRequest.cs +│ ├── GenerateTtsRequest.cs +│ └── ... +│ +├── Data/ +│ ├── AppDbContext.cs -- Entity Framework Core context +│ ├── Migrations/ -- EF Core migrations +│ └── SeedData.cs -- Initial data seeding +│ +├── Utilities/ +│ ├── AudioHelper.cs -- Audio file handling +│ ├── ValidationHelper.cs -- Input validation +│ └── ... +│ +├── appsettings.json -- Configuration (DB, Vosk, Coqui, Mistral) +└── Program.cs -- Startup and middleware \ No newline at end of file diff --git a/docs/architecture/frontend-structure.md b/docs/architecture/frontend-structure.md new file mode 100644 index 0000000..3204000 --- /dev/null +++ b/docs/architecture/frontend-structure.md @@ -0,0 +1,119 @@ +german-app-frontend/ +│ +├── public/ # Static files +│ ├── audio/ # Generated audio files (served statically) +│ │ ├── vocabulary/ # Vocabulary word audio (e.g., 1.wav, 2.wav) +│ │ ├── story/ # Story segment audio +│ │ └── quiz/ # Quiz question audio +│ │ +│ ├── favicon.ico +│ └── index.html # Single HTML entry point +│ +├── src/ # Source code +│ │ +│ ├── assets/ # Static assets (images, fonts, etc.) +│ │ ├── images/ +│ │ │ ├── badges/ # Badge icons +│ │ │ ├── flags/ # Language flags +│ │ │ └── ... +│ │ └── fonts/ +│ │ +│ ├── components/ # Reusable UI components +│ │ │ +│ │ ├── common/ # Generic, reusable components +│ │ │ ├── AudioPlayer.tsx # Plays audio files (vocabulary, story, quiz) +│ │ │ ├── Recorder.tsx # Records user speech for speaking exercises +│ │ │ ├── Button.tsx # Reusable button with variants +│ │ │ ├── Modal.tsx # Modal dialog +│ │ │ ├── Spinner.tsx # Loading spinner +│ │ │ ├── Card.tsx # Reusable card component +│ │ │ └── index.ts # Exports all common components +│ │ │ +│ │ ├── layout/ # Layout-related components +│ │ │ ├── Header.tsx # Top navigation bar +│ │ │ ├── Footer.tsx # Footer component +│ │ │ ├── Sidebar.tsx # Sidebar for navigation (if needed) +│ │ │ └── Layout.tsx # Main layout wrapper +│ │ │ +│ │ ├── lesson/ # Lesson-specific components +│ │ │ ├── VocabularyTab.tsx # Displays vocabulary words with audio +│ │ │ ├── GrammarTab.tsx # Displays grammar explanations +│ │ │ ├── StoryTab.tsx # Displays story segments with audio +│ │ │ ├── PracticeTab.tsx # Speaking/writing/listening exercises +│ │ │ └── index.ts # Exports lesson components +│ │ │ +│ │ ├── quiz/ # Quiz-specific components +│ │ │ ├── McqQuestion.tsx # Multiple-choice question +│ │ │ ├── FillInBlank.tsx # Fill-in-the-blank question +│ │ │ ├── ListeningQuestion.tsx # Listening comprehension question +│ │ │ ├── MatchingQuestion.tsx # Matching question (e.g., word to article) +│ │ │ ├── QuizResults.tsx # Displays quiz results +│ │ │ └── index.ts # Exports quiz components +│ │ │ +│ │ ├── story/ # Story-specific components +│ │ │ ├── StorySegment.tsx # Displays a single story segment +│ │ │ └── StoryPlayer.tsx # Handles story audio playback +│ │ │ +│ │ ├── dashboard/ # Dashboard-specific components +│ │ │ ├── ProgressBar.tsx # Shows progress for a level +│ │ │ ├── LessonCard.tsx # Card for a lesson (title, status, etc.) +│ │ │ ├── StoryProgress.tsx # Shows story unlock progress +│ │ │ ├── BadgeDisplay.tsx # Displays earned badges +│ │ │ └── index.ts +│ │ │ +│ │ ├── practice/ # Practice-specific components +│ │ │ ├── SpeakingExercise.tsx # Speaking practice with recorder +│ │ │ ├── WritingExercise.tsx # Writing practice with Mistral feedback +│ │ │ └── ListeningExercise.tsx # Listening practice +│ │ │ +│ │ └── shared/ # Shared logic/components +│ │ ├── ProtectedRoute.tsx # Wraps routes that require auth +│ │ ├── PrivateRoute.tsx # Alternative name for ProtectedRoute +│ │ └── ErrorBoundary.tsx # Catches and displays errors +│ │ +│ ├── context/ # React Context providers +│ │ ├── AuthContext.tsx # Manages authentication state +│ │ ├── UserProgressContext.tsx # Manages user progress (optional) +│ │ └── index.ts # Exports all contexts +│ │ +│ ├── hooks/ # Custom React hooks +│ │ ├── useAuth.ts # Manages auth state (login, logout, etc.) +│ │ ├── useAudio.ts # Handles audio playback logic +│ │ ├── useRecorder.ts # Handles audio recording logic +│ │ ├── useApi.ts # Axios API client with interceptors +│ │ ├── useLessons.ts # Fetches and manages lesson data +│ │ ├── useQuizzes.ts # Fetches and manages quiz data +│ │ ├── useUserProgress.ts # Fetches and manages user progress +│ │ ├── useStory.ts # Fetches and manages story segments +│ │ └── index.ts # Exports all hooks +│ │ +│ ├── pages/ # Page-level components (routes) +│ │ ├── DashboardPage.tsx # User dashboard (progress, next lesson, etc.) +│ │ ├── LessonPage.tsx # Lesson page (tabs for vocabulary, grammar, story, quiz) +│ │ ├── QuizPage.tsx # Standalone quiz page (if needed) +│ │ ├── StoryPage.tsx # Full story viewer (all unlocked segments) +│ │ ├── PracticePage.tsx # Practice exercises (speaking, writing, listening) +│ │ ├── ProfilePage.tsx # User profile (badges, settings, etc.) +│ │ ├── LoginPage.tsx # Login form +│ │ ├── RegisterPage.tsx # Registration form +│ │ ├── NotFoundPage.tsx # 404 page +│ │ └── index.ts # Exports all pages +│ │ +│ ├── services/ # API service layer +│ │ ├── api.ts # Axios instance with interceptors +│ │ ├── authService.ts # Authentication API calls +│ │ ├── lessonService.ts # Lesson-related API calls +│ │ ├── quizService.ts # Quiz-related API calls +│ │ ├── storyService.ts # Story-related API calls +│ │ ├── speechService.ts # Speech recognition API calls +│ │ ├── ttsService.ts # TTS API calls +│ │ ├── userProgressService.ts # User progress API calls +│ │ └── index.ts # Exports all services +│ │ +│ ├── store/ # State management (optional, if using Zustand/Redux) +│ │ ├── slices/ # Redux slices (if using Redux) +│ │ │ ├── authSlice.ts +│ │ │ ├── userSlice.ts +│ │ │ └── ... +│ │ ├── store.ts # Redux store configuration +│ │ └── hooks.ts # Redux hooks \ No newline at end of file diff --git a/docs/database/initial-database-schema.sql b/docs/database/initial-database-schema.sql new file mode 100644 index 0000000..2cbcc5e --- /dev/null +++ b/docs/database/initial-database-schema.sql @@ -0,0 +1,242 @@ +-- Enable UUID extension (optional, but useful for unique IDs) +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +--- +--- **1. Users and Authentication** +--- + +-- Users table +CREATE TABLE Users ( + Id SERIAL PRIMARY KEY, + Username VARCHAR(50) UNIQUE NOT NULL, + Email VARCHAR(100) UNIQUE NOT NULL, + PasswordHash VARCHAR(255) NOT NULL, + Role VARCHAR(20) DEFAULT 'user' CHECK (Role IN ('user', 'admin')), + LastLoginAt TIMESTAMP, + CreatedAt TIMESTAMP DEFAULT NOW() +); + +--- +--- **2. Learning Content: Levels, Lessons, and Dependencies** +--- + +-- Levels table +CREATE TABLE Levels ( + Id SERIAL PRIMARY KEY, + Name VARCHAR(10) UNIQUE NOT NULL, -- e.g., "A1", "A2" + Description TEXT, + Order INT UNIQUE NOT NULL, + IsActive BOOLEAN DEFAULT TRUE +); + +-- Lessons table +CREATE TABLE Lessons ( + Id SERIAL PRIMARY KEY, + LevelId INT REFERENCES Levels(Id) ON DELETE CASCADE, + Title VARCHAR(100) NOT NULL, + Description TEXT, + Order INT NOT NULL, + EstimatedMinutes INT, + UNIQUE(LevelId, Order) +); + +--- +--- **3. Vocabulary and Word Details** +--- + +-- Vocabulary table (core words) +CREATE TABLE Vocabulary ( + Id SERIAL PRIMARY KEY, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + BaseWord VARCHAR(50) NOT NULL, -- e.g., "gehen", "Buch" + UNIQUE(LessonId, BaseWord) +); + +-- WordMetadata table (metadata for vocabulary, e.g., frequency, part of speech) +CREATE TABLE WordMetadata ( + Id SERIAL PRIMARY KEY, + VocabularyId INT REFERENCES Vocabulary(Id) ON DELETE CASCADE, + Translation VARCHAR(100) NOT NULL, + WordClass VARCHAR(20) NOT NULL CHECK (WordClass IN ('noun', 'verb', 'adjective', 'adverb', 'preposition', 'conjunction', 'pronoun', 'article', 'other')), + Frequency INT CHECK (Frequency BETWEEN 1 AND 5), -- 1 = rare, 5 = very common + AudioUrl VARCHAR(255) NOT NULL, + ImageUrl VARCHAR(255) +); + +-- WordForms table (inflections, plurals, conjugations, etc.) +CREATE TABLE WordForms ( + Id SERIAL PRIMARY KEY, + VocabularyId INT REFERENCES Vocabulary(Id) ON DELETE CASCADE, + FormType VARCHAR(20) NOT NULL CHECK (FormType IN ('plural', 'singular', 'infinitive', 'past_tense', 'past_participle', 'present_participle', 'conjugation_ich', 'conjugation_du', 'conjugation_er', 'conjugation_wir', 'conjugation_ihr', 'conjugation_sie', 'dative', 'accusative', 'genitive', 'nominative')), + Form VARCHAR(50) NOT NULL, + Article VARCHAR(10) CHECK (Article IN ('der', 'die', 'das', '')), -- Only for nouns + AudioUrl VARCHAR(255), + UNIQUE(VocabularyId, FormType, Form) +); + +--- +--- **4. Story Segments** +--- + +-- StorySegments table +CREATE TABLE StorySegments ( + Id SERIAL PRIMARY KEY, + LevelId INT REFERENCES Levels(Id) ON DELETE CASCADE, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE NOT NULL, -- Required + Title VARCHAR(100) NOT NULL, + Content TEXT NOT NULL, + AudioUrl VARCHAR(255) NOT NULL, + Order INT NOT NULL, + IsUnlocked BOOLEAN DEFAULT FALSE, -- Managed by the app logic (unlocked via lesson completion) + UNIQUE(LevelId, Order) +); + +--- +--- **5. Quizzes and Questions** +--- + +-- Quizzes table +CREATE TABLE Quizzes ( + Id SERIAL PRIMARY KEY, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + Title VARCHAR(100) NOT NULL, + Description TEXT, + PassingScore INT DEFAULT 80 +); + +-- Questions table +CREATE TABLE Questions ( + Id SERIAL PRIMARY KEY, + QuizId INT REFERENCES Quizzes(Id) ON DELETE CASCADE, + Type VARCHAR(20) NOT NULL CHECK (Type IN ('mcq', 'fill_in', 'listening', 'matching')), + Content TEXT NOT NULL, + CorrectAnswer TEXT NOT NULL, + AudioUrl VARCHAR(255), + AudioScript TEXT, -- For listening questions: the text of the audio + Explanation TEXT, + Points INT DEFAULT 1, + Difficulty INT CHECK (Difficulty BETWEEN 1 AND 5) +); + +-- QuestionOptions table (normalized for MCQ) +CREATE TABLE QuestionOptions ( + Id SERIAL PRIMARY KEY, + QuestionId INT REFERENCES Questions(Id) ON DELETE CASCADE, + OptionText TEXT NOT NULL, + Order INT NOT NULL, + UNIQUE(QuestionId, Order) +); + +--- +--- **6. User Progress and Quiz Attempts** +--- + +-- UserProgress table +CREATE TABLE UserProgress ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + IsCompleted BOOLEAN DEFAULT FALSE, + CurrentLevelId INT REFERENCES Levels(Id), -- Moved from Users + Streak INT DEFAULT 0, + TotalPoints INT DEFAULT 0, + TimeSpentMinutes INT DEFAULT 0, + LastVisitedAt TIMESTAMP DEFAULT NOW(), + UNIQUE(UserId, LessonId) +); + +-- QuizAttempts table (tracks all attempts) +CREATE TABLE QuizAttempts ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + QuizId INT REFERENCES Quizzes(Id) ON DELETE CASCADE, + Score INT NOT NULL, + TotalPoints INT NOT NULL, + Passed BOOLEAN NOT NULL, + AttemptDate TIMESTAMP DEFAULT NOW(), + TimeSpentSeconds INT +); + +--- +--- **7. Gamification** +--- + +-- Badges table +CREATE TABLE Badges ( + Id SERIAL PRIMARY KEY, + Name VARCHAR(50) NOT NULL, + Description TEXT, + ImageUrl VARCHAR(255), + PointsRequired INT, + Category VARCHAR(50), + UNIQUE(Name) +); + +-- UserBadges table +CREATE TABLE UserBadges ( + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + BadgeId INT REFERENCES Badges(Id) ON DELETE CASCADE, + EarnedDate TIMESTAMP DEFAULT NOW(), + PRIMARY KEY (UserId, BadgeId) +); + +--- +--- **8. Practice Sessions** +--- + +-- PracticeSessions table +CREATE TABLE PracticeSessions ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + Type VARCHAR(20) NOT NULL CHECK (Type IN ('speaking', 'writing')), + Prompt TEXT NOT NULL, + UserResponse TEXT, + Feedback TEXT, + Score INT, + TimeSpentSeconds INT, + Date TIMESTAMP DEFAULT NOW() +); + +--- +--- **9. User Settings** +--- + +-- UserSettings table +CREATE TABLE UserSettings ( + UserId INT PRIMARY KEY REFERENCES Users(Id) ON DELETE CASCADE, + AudioAutoplay BOOLEAN DEFAULT TRUE, + Theme VARCHAR(20) DEFAULT 'light' CHECK (Theme IN ('light', 'dark')), + NativeLanguage VARCHAR(50) DEFAULT 'en' +); + +--- +--- **10. Feedback** +--- + +-- Feedback table +CREATE TABLE Feedback ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + LessonId INT REFERENCES Lessons(Id) ON DELETE SET NULL, + QuizId INT REFERENCES Quizzes(Id) ON DELETE SET NULL, + Rating INT CHECK (Rating BETWEEN 1 AND 5), + Comment TEXT, + CreatedAt TIMESTAMP DEFAULT NOW(), + CHECK (LessonId IS NOT NULL OR QuizId IS NOT NULL) +); + +--- +--- **11. Indexes for Performance** +--- + +CREATE INDEX idx_userprogress_userid ON UserProgress(UserId); +CREATE INDEX idx_userprogress_lessonid ON UserProgress(LessonId); +CREATE INDEX idx_vocabulary_lessonid ON Vocabulary(LessonId); +CREATE INDEX idx_wordmetadata_vocabularyid ON WordMetadata(VocabularyId); +CREATE INDEX idx_wordforms_vocabularyid ON WordForms(VocabularyId); +CREATE INDEX idx_questions_quizid ON Questions(QuizId); +CREATE INDEX idx_questionoptions_questionid ON QuestionOptions(QuestionId); +CREATE INDEX idx_quizattempts_userid ON QuizAttempts(UserId); +CREATE INDEX idx_quizattempts_quizid ON QuizAttempts(QuizId); +CREATE INDEX idx_storysegments_levelid ON StorySegments(LevelId); +CREATE INDEX idx_storysegments_lessonid ON StorySegments(LessonId); \ No newline at end of file diff --git a/docs/development/how-to-run-vibe.md b/docs/development/how-to-run-vibe.md new file mode 100644 index 0000000..92460ee --- /dev/null +++ b/docs/development/how-to-run-vibe.md @@ -0,0 +1,2 @@ +source ~/.venv/mistral-vibe/bin/activate +vibe \ No newline at end of file diff --git a/docs/features/README.md b/docs/features/README.md new file mode 100644 index 0000000..724dcca --- /dev/null +++ b/docs/features/README.md @@ -0,0 +1,373 @@ +# Feature Tracking & Implementation Plans + +This directory contains implementation plans and progress tracking for features in the **DeutschLernen** solution. Each feature follows a comprehensive template with **Definition of Done**, **Testing Strategy**, and detailed technical design. + +--- + +## 📋 Feature Lifecycle + +Each feature goes through the following stages: + +``` +Backlog → Planned → In Progress → Code Review → Completed + ↓ ↓ ↓ + (Prioritized) (Active Dev) (Testing/QA) +``` + +--- + +## 📁 Structure + +``` +features/ +├── README.md # This file - Feature tracking overview & roadmap +├── template.md # Template for new feature implementation plans +├── ai-services.md # Mistral, Vosk, Coqui TTS integration +├── frontend-ui.md # React + TypeScript frontend application +├── gamification.md # Points, badges, streaks system +├── infrastructure-setup.md # Backend, DB, Docker, CI/CD foundation +├── lesson-management.md # Lessons, Levels, Progress tracking +├── quiz-system.md # Multiple question types, scoring, results +├── story-integration.md # AI-generated stories with audio +├── user-authentication.md # JWT-based auth system +└── vocabulary-system.md # Words, articles, audio, import +``` + +Each feature file contains: +- 📌 **Overview** - Purpose, user stories, acceptance criteria +- 📋 **Requirements** - Functional & non-functional with priorities +- 🏗️ **Technical Design** - Architecture, data flow, API endpoints +- 🚀 **Implementation Plan** - Phases, milestones, tasks +- ✅ **Definition of Done** - General + feature-specific criteria +- 🧪 **Testing Strategy** - Test types, tools, feature-specific test cases +- 📝 **Notes & Decisions** - Decisions, technical notes, gotchas +- 🔗 **Related Files** - Cross-references to architecture docs + +**AI Services** also includes: +- 🚨 **Risks & Mitigations** - 18 identified risks with owners +- 🔧 **Technical Deep Dive** - Python-.NET integration patterns with code examples + +--- + +## 🗺️ Development Roadmap + +Based on dependencies and complexity, here's the recommended implementation order: + +### Phase 1: Foundation (Weeks 1-2) +**Total: ~30-42 hours** | **Prerequisite: None** + +| # | Feature | Priority | Estimate | Dependencies | Status | +|---|---------|----------|----------|--------------|--------| +| 1 | [Infrastructure Setup](infrastructure-setup.md) | High | 10-14h | None | ⏳ Planned | +| 2 | [User Authentication](user-authentication.md) | High | 4-6h | Infrastructure | ⏳ Planned | + +**Goal:** Have a working backend project, database, and authentication system. + +--- + +### Phase 2: Core Backend (Weeks 3-4) +**Total: ~42-58 hours** | **Prerequisite: Phase 1** + +| # | Feature | Priority | Estimate | Dependencies | Status | +|---|---------|----------|----------|--------------|--------| +| 3 | [Lesson Management](lesson-management.md) | High | 10-16h | Infrastructure, Auth | ⏳ Planned | +| 4 | [AI Services](ai-services.md) | High | 10-16h | Infrastructure | ⏳ Planned | +| 5 | [Vocabulary System](vocabulary-system.md) | High | 8-12h | Infrastructure, Lessons | ⏳ Planned | +| 6 | [Quiz System](quiz-system.md) | High | 6-10h | Infrastructure, Lessons | ⏳ Planned | + +**Goal:** Have all core backend functionality working with AI integration. + +--- + +### Phase 3: Content & Features (Weeks 5-6) +**Total: ~30-42 hours** | **Prerequisite: Phase 2** + +| # | Feature | Priority | Estimate | Dependencies | Status | +|---|---------|----------|----------|--------------|--------| +| 7 | [Story Integration](story-integration.md) | High | 8-12h | Lessons, AI Services | ⏳ Planned | +| 8 | [Gamification](gamification.md) | Medium | 6-8h | Auth, Lessons, Quiz | ⏳ Planned | + +**Goal:** Have all content management and gamification features working. + +--- + +### Phase 4: Frontend (Weeks 7-8) +**Total: ~10-16 hours** | **Prerequisite: Phase 2-3** + +| # | Feature | Priority | Estimate | Dependencies | Status | +|---|---------|----------|----------|--------------|--------| +| 9 | [Frontend UI](frontend-ui.md) | High | 10-16h | All backend features | ⏳ Planned | + +**Goal:** Complete frontend application with all UI components and pages. + +--- + +## 📊 Complete Timeline + +``` +Week 1-2: Phase 1 - Foundation (30-42h) + │ + ├── Infrastructure Setup (10-14h) + └── User Authentication (4-6h) + +Week 3-4: Phase 2 - Core Backend (42-58h) + │ + ├── Lesson Management (10-16h) + ├── AI Services (10-16h) + ├── Vocabulary System (8-12h) + └── Quiz System (6-10h) + +Week 5-6: Phase 3 - Content & Features (30-42h) + │ + ├── Story Integration (8-12h) + └── Gamification (6-8h) + +Week 7-8: Phase 4 - Frontend (10-16h) + │ + └── Frontend UI (10-16h) +``` + +**Total Estimated Time: 112-158 hours (~3-4 weeks for a small team)** + +--- + +## 🎯 Dependency Graph + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ DEPENDENCY GRAPH │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │Infrastructure │ │User Auth │ │ +│ │ Setup │ │ │ │ +│ └────────┬─────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ └─────────────────────────┴─────────────────┐ │ +│ │ │ │ +│ ┌──────────────────┬──────────────────┬───────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────────┐ ┌─────────────┐ ┌──────────────┐ │ +│ │Lesson │ │AI Services │ │Vocabulary │ │ +│ │Management │ │ │ │System │ │ +│ └────────┬─────────┘ └──────┬──────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ Quiz System │ │ +│ └────────────────────────┬────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────┬──────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ ┌─────────┐ │ +│ │Story │ │Gamification │ │Frontend │ │ +│ │Integration │ │ │ │UI │ │ +│ └──────────────┘ └──────────────┘ └─────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +LEGEND: + ▼ = Dependency (must be completed first) + → = Data flow +``` + +--- + +## 📈 Resource Allocation (Sample) + +### Team of 2 Developers (4 weeks) + +| Week | Developer A | Developer B | Total Hours | +|------|-------------|-------------|-------------| +| 1 | Infrastructure Setup (14h) | User Auth (6h) + Start Lesson Mgmt (4h) | 20h | +| 2 | Lesson Mgmt (12h) | AI Services (16h) | 28h | +| 3 | Vocabulary (12h) + Quiz (6h) | AI Services (remaining 4h) + Story (8h) | 28h | +| 4 | Gamification (8h) + Frontend (8h) | Frontend (8h) + Testing | 24h | + +### Solo Developer (8-10 weeks) + +| Week | Focus | Hours | +|------|-------|-------| +| 1-2 | Infrastructure + Auth | 20h | +| 3-4 | Lesson Management + AI Services | 32h | +| 5 | Vocabulary + Quiz | 22h | +| 6 | Story Integration + Gamification | 20h | +| 7-8 | Frontend UI | 16h | +| 9-10 | Testing, Polish, Bug Fixes | 20h | + +--- + +## 🎯 Quick Start Guide + +### 1. Pick Your Starting Point + +Based on your role: + +| Role | Start With | Why | +|------|------------|-----| +| Backend Dev | Infrastructure Setup | Foundation for everything | +| Full-Stack | Infrastructure Setup | Need backend first | +| Frontend Dev | Wait for Phase 2 | Backend must be ready | +| DevOps | Infrastructure Setup | CI/CD, Docker setup | +| QA | Review all feature plans | Understand what to test | + +### 2. Feature Implementation Checklist + +For each feature: + +```markdown +# Feature: [Name] + +## Before Starting +- [ ] Read feature plan thoroughly +- [ ] Review dependencies - are they complete? +- [ ] Review risks - any blockers? +- [ ] Review tasks - understand scope +- [ ] Create branch: feature/[feature-name] + +## During Implementation +- [ ] Follow Clean Architecture principles +- [ ] Write tests alongside code (TDD) +- [ ] Update feature plan with progress +- [ ] Document decisions in Notes & Decisions +- [ ] Commit frequently with Conventional Commits + +## Before PR +- [ ] All tasks completed +- [ ] All acceptance criteria met +- [ ] All tests passing +- [ ] Code reviewed (self-review first) +- [ ] Update feature plan status to 🔄 Code Review +- [ ] Add PR link to feature plan + +## After Merge +- [ ] Update feature plan status to ✅ Completed +- [ ] Add completion date +- [ ] Document lessons learned +- [ ] Update roadmap progress +``` + +### 3. Daily Workflow + +```bash +# Start day +cd /home/lasserh/Projects/DeutschLernen +git pull origin main + +# Pick a feature +grep -l "🚀 In Progress" docs/features/*.md + +# Work on tasks +# ... coding ... + +# Check progress +grep -A 20 "## ✅ Tasks" docs/features/[feature-name].md + +# End of day +# - Update task statuses +# - Update progress in feature plan +# - Push branch +git add . +git commit -m "feat([feature]): [description]" +git push +``` + +--- + +## 📊 Current Features Status + +### Phase 1: Foundation (Weeks 1-2) + +| Feature | Status | Priority | Estimate | Assignee | Dependencies | Start Date | Target Completion | +|---------|--------|----------|----------|----------|--------------|------------|-------------------| +| [infrastructure-setup.md](infrastructure-setup.md) | ⏳ Planned | High | 10-14h | - | None | - | - | +| [user-authentication.md](user-authentication.md) | ⏳ Planned | High | 4-6h | - | Infrastructure | - | - | + +### Phase 2: Core Backend (Weeks 3-4) + +| Feature | Status | Priority | Estimate | Assignee | Dependencies | Start Date | Target Completion | +|---------|--------|----------|----------|----------|--------------|------------|-------------------| +| [lesson-management.md](lesson-management.md) | ⏳ Planned | High | 10-16h | - | Infrastructure, Auth | - | - | +| [ai-services.md](ai-services.md) | ⏳ Planned | High | 10-16h | - | Infrastructure | - | - | +| [vocabulary-system.md](vocabulary-system.md) | ⏳ Planned | High | 8-12h | - | Infrastructure, Lessons | - | - | +| [quiz-system.md](quiz-system.md) | ⏳ Planned | High | 6-10h | - | Infrastructure, Lessons | - | - | + +### Phase 3: Content & Features (Weeks 5-6) + +| Feature | Status | Priority | Estimate | Assignee | Dependencies | Start Date | Target Completion | +|---------|--------|----------|----------|----------|--------------|------------|-------------------| +| [story-integration.md](story-integration.md) | ⏳ Planned | High | 8-12h | - | Lessons, AI Services | - | - | +| [gamification.md](gamification.md) | ⏳ Planned | Medium | 6-8h | - | Auth, Lessons, Quiz | - | - | + +### Phase 4: Frontend (Weeks 7-8) + +| Feature | Status | Priority | Estimate | Assignee | Dependencies | Start Date | Target Completion | +|---------|--------|----------|----------|----------|--------------|------------|-------------------| +| [frontend-ui.md](frontend-ui.md) | ⏳ Planned | High | 10-16h | - | All backend features | - | - | + +--- + +## 🔍 Quick Links + +| Feature | Quick Access | Priority | +|---------|--------------|----------| +| Infrastructure Setup | [View](infrastructure-setup.md) | ⭐⭐⭐⭐⭐ | +| User Authentication | [View](user-authentication.md) | ⭐⭐⭐⭐⭐ | +| Lesson Management | [View](lesson-management.md) | ⭐⭐⭐⭐⭐ | +| AI Services | [View](ai-services.md) | ⭐⭐⭐⭐⭐ | +| Vocabulary System | [View](vocabulary-system.md) | ⭐⭐⭐⭐ | +| Quiz System | [View](quiz-system.md) | ⭐⭐⭐⭐ | +| Story Integration | [View](story-integration.md) | ⭐⭐⭐⭐ | +| Gamification | [View](gamification.md) | ⭐⭐⭐ | +| Frontend UI | [View](frontend-ui.md) | ⭐⭐⭐⭐ | + +--- + +## 📈 Progress Tracking Commands + +```bash +# List all features +ls -1 docs/features/*.md | grep -v README | grep -v template + +# Count features by status +for status in "Planned" "In Progress" "Code Review" "Completed"; do + echo "$status: $(grep -l "Status.*$status" docs/features/*.md | wc -l)" +done + +# Find features ready to start (dependencies met) +# (This requires manual checking against dependency list) + +# Get total estimated hours +grep "Estimate:" docs/features/*.md | grep -oP "\d+-\d+h" | \ + awk -F'-' '{sum+=$1+$2} END {print "Total estimate: " sum/2 " hours"}' +``` + +--- + +## 💡 Best Practices + +1. **Start with Infrastructure** - Everything depends on it +2. **Complete dependencies first** - Don't get blocked mid-feature +3. **Update as you go** - Keep feature plans in sync with progress +4. **Test as you build** - Follow the TDD approach in the application plan +5. **Document decisions** - Future you will thank present you +6. **Review before merging** - Use the Definition of Done as a checklist +7. **Celebrate completions** - Update status to ✅ and reflect on lessons learned + +--- + +## 📚 Related Documentation + +- [AGENTS.md](../AGENTS.md) - Development rules and workflows +- [Application Plan](../architecture/application-plan.md) - Overall project vision +- [Backend Structure](../architecture/backend-structure.md) - Technical architecture +- [Frontend Structure](../architecture/frontend-structure.md) - Frontend architecture +- [Database Schema](../database/initial-database-schema.sql) - Database design + +--- + +*Last updated: May 31, 2025* +*Feature tracking system based on Clean Architecture and TDD principles* diff --git a/docs/features/ai-services.md b/docs/features/ai-services.md new file mode 100644 index 0000000..0846512 --- /dev/null +++ b/docs/features/ai-services.md @@ -0,0 +1,896 @@ +# Feature: AI Services Integration + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: High +> **Estimate**: 10-16 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Story Integration, Vocabulary System, Quiz System, Lesson Management + +--- + +## 📌 Overview + +### Purpose +Integrate three AI services into the application: Mistral-Medium for text generation (stories, feedback), Vosk for speech recognition (speaking exercises), and Coqui TTS for text-to-speech (vocabulary, stories, quizzes). + +### User Story +As a learner, I want AI-powered features like generated stories, speech recognition for speaking practice, and TTS for audio content so that I can have an immersive and interactive learning experience. + +### Acceptance Criteria +- [ ] Mistral-Medium API is integrated for story generation +- [ ] Mistral-Medium API is integrated for writing feedback +- [ ] Vosk speech recognition is integrated for speaking exercises +- [ ] Coqui TTS is integrated for audio generation +- [ ] All AI services are configurable via appsettings.json +- [ ] Error handling for AI service failures +- [ ] Rate limiting/caching for AI API calls + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | Generate stories using Mistral-Medium | High | +| FR-002 | Generate writing feedback using Mistral-Medium | High | +| FR-003 | Transcribe speech using Vosk | High | +| FR-004 | Generate audio using Coqui TTS | High | +| FR-005 | Configure all services via configuration | High | +| FR-006 | Handle AI service errors gracefully | High | +| FR-007 | Cache/rate limit AI API calls | Medium | +| FR-008 | Validate AI outputs before use | Medium | + +### Non-Functional Requirements +- Performance: TTS generation < 2 seconds per sentence +- Performance: Speech recognition < 3 seconds +- Performance: AI API calls < 5 seconds +- Reliability: Services should degrade gracefully on failure +- Cost: Minimize API call costs (caching, batching) + +--- + +## 🏗️ Technical Design + +### Architecture Overview +``` +┌─────────────────────────────────────────────────────────────┐ +│ AI Services Layer │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Mistral-Medium │ │ Vosk │ │ Coqui TTS │ │ +│ │ (Text Gen) │ │ (Speech Recog.) │ │ (Audio Gen) │ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Application Services │ │ +│ │ - StoryGenerationService │ │ +│ │ - WritingFeedbackService │ │ +│ │ - VoskService (Speech Recognition) │ │ +│ │ - TtsService (Text-to-Speech) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Components Involved +- **Backend Services**: + - `IMistralService` / `MistralService` - Text generation + - `IVoskService` / `VoskService` - Speech recognition + - `ITtsService` / `TtsService` - Text-to-speech +- **Configuration**: appsettings.json with AI settings +- **External Dependencies**: + - Mistral-Medium API + - Vosk Python library + German model + - Coqui TTS Python library + German model + +### Data Flow + +#### Story Generation Flow +``` +1. StoryGenerationService receives request with vocabulary list and level +2. Service constructs prompt for Mistral-Medium +3. MistralService sends prompt to Mistral API +4. Mistral API returns generated story text +5. StoryGenerationService validates and returns story +6. StoryService saves story and triggers audio generation +``` + +#### Speech Recognition Flow +``` +1. User records speech in frontend +2. Frontend sends audio file to /api/speech/recognize +3. VoskService receives audio bytes +4. VoskService calls Vosk Python CLI with German model +5. Vosk returns transcribed text +6. Backend validates transcription and returns to frontend +``` + +#### TTS Flow +``` +1. TtsService receives text to synthesize +2. Service calls Coqui TTS Python CLI +3. Coqui generates audio file +4. Audio file saved to filesystem +5. Audio URL returned to caller +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Configuration & Interfaces (2 hours) +- [ ] Add AI configuration section to appsettings.json +- [ ] Create configuration classes (MistralConfig, VoskConfig, CoquiConfig) +- [ ] Define service interfaces (IMistralService, IVoskService, ITtsService) +- [ ] Register services in Program.cs +- [ ] Set up configuration validation + +### Phase 2: Mistral-Medium Integration (2-3 hours) +- [ ] Create MistralService implementation +- [ ] Implement Mistral API client +- [ ] Create request/response models +- [ ] Implement retry logic for API calls +- [ ] Add rate limiting (e.g., max 10 requests/minute) +- [ ] Add response caching for similar prompts +- [ ] Create prompt templates for different use cases + +### Phase 3: Vosk Speech Recognition (2-3 hours) +- [ ] Create VoskService implementation +- [ ] Set up Vosk Python environment +- [ ] Download and configure German model (vosk-model-de-0.22) +- [ ] Implement audio processing +- [ ] Handle different audio formats +- [ ] Add error handling for recognition failures +- [ ] Create /api/speech/recognize endpoint + +### Phase 4: Coqui TTS Integration (2-3 hours) +- [ ] Create TtsService implementation +- [ ] Set up Coqui TTS Python environment +- [ ] Download and configure German model +- [ ] Implement audio generation +- [ ] Add audio file management (storage, cleanup) +- [ ] Create audio serving endpoints +- [ ] Implement batch audio generation + +### Phase 5: Service Integration (2 hours) +- [ ] Create StoryGenerationService (uses MistralService) +- [ ] Create WritingFeedbackService (uses MistralService) +- [ ] Create SpeechExerciseService (uses VoskService) +- [ ] Create AudioGenerationService (uses TtsService) +- [ ] Add health checks for all AI services +- [ ] Implement fallback mechanisms for service failures + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Configuration & Interfaces | - | ⏳ | +| Mistral Integration | - | ⏳ | +| Vosk Integration | - | ⏳ | +| Coqui TTS Integration | - | ⏳ | +| Service Integration | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend - Configuration +- [ ] Add Mistral settings to appsettings.json +- [ ] Add Vosk settings to appsettings.json +- [ ] Add Coqui settings to appsettings.json +- [ ] Create Configuration/MistralConfig.cs +- [ ] Create Configuration/VoskConfig.cs +- [ ] Create Configuration/CoquiConfig.cs +- [ ] Register all AI services in Program.cs +- [ ] Add health checks for AI services + +### Backend - Mistral Service +- [ ] Create Domain/Interfaces/IMistralService.cs +- [ ] Create Infrastructure/Services/MistralService.cs +- [ ] Implement Mistral API client +- [ ] Create Models/MistralRequest.cs +- [ ] Create Models/MistralResponse.cs +- [ ] Add retry logic +- [ ] Add rate limiting +- [ ] Add response caching +- [ ] Write unit tests + +### Backend - Vosk Service +- [ ] Create Domain/Interfaces/IVoskService.cs +- [ ] Create Infrastructure/Services/VoskService.cs +- [ ] Set up Python process execution +- [ ] Download and configure vosk-model-de-0.22 +- [ ] Implement audio recognition +- [ ] Create /api/speech/recognize endpoint +- [ ] Create Presentation/Controllers/SpeechController.cs +- [ ] Write unit tests + +### Backend - Coqui TTS Service +- [ ] Create Domain/Interfaces/ITtsService.cs +- [ ] Create Infrastructure/Services/TtsService.cs +- [ ] Set up Python process execution +- [ ] Download and configure Coqui German model +- [ ] Implement audio generation +- [ ] Create audio file storage mechanism +- [ ] Create /api/tts/generate endpoint +- [ ] Create Presentation/Controllers/TtsController.cs +- [ ] Write unit tests + +### Backend - Higher-Level Services +- [ ] Create Application/Services/StoryGenerationService.cs +- [ ] Create Application/Services/WritingFeedbackService.cs +- [ ] Integrate with MistralService +- [ ] Add validation for AI outputs +- [ ] Write integration tests + +### Infrastructure Setup +- [ ] Install Python 3.8+ +- [ ] Install Vosk Python package +- [ ] Download vosk-model-de-0.22 +- [ ] Install Coqui TTS package +- [ ] Download Coqui German model +- [ ] Set up file storage for audio +- [ ] Configure permissions + +### Frontend Integration +- [ ] Create services/speechService.ts +- [ ] Create services/ttsService.ts +- [ ] Create services/aiService.ts +- [ ] Integrate with Recorder component +- [ ] Integrate with AudioPlayer component +- [ ] Add error handling for AI failures + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### AI-Specific Criteria +- [ ] All AI services functional in development +- [ ] Mistral API integration tested with valid API key +- [ ] Vosk speech recognition tested with German model +- [ ] Coqui TTS tested with German model +- [ ] Error handling tested (invalid inputs, service failures) +- [ ] Fallback mechanisms implemented and tested +- [ ] Rate limiting configured and tested +- [ ] Audio file generation and storage verified +- [ ] Health checks for all AI services passing + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | +| Load Testing | AI service performance | k6/JMeter | DevOps | + +### AI-Specific Tests + +#### Mistral Service Tests +- [ ] Test successful text generation +- [ ] Test API error handling (429, 500, 503) +- [ ] Test rate limiting (max requests per minute) +- [ ] Test response caching +- [ ] Test retry logic on failures +- [ ] Test timeout handling +- [ ] Test invalid API key handling + +#### Vosk Service Tests +- [ ] Test successful speech recognition (clear audio) +- [ ] Test speech recognition with background noise +- [ ] Test speech recognition with different accents +- [ ] Test empty audio handling +- [ ] Test invalid audio format handling +- [ ] Test Python process failure handling +- [ ] Test model not found error handling +- [ ] Test confidence threshold validation + +#### Coqui TTS Service Tests +- [ ] Test successful audio generation +- [ ] Test audio generation with long text +- [ ] Test audio generation with special characters +- [ ] Test invalid text handling +- [ ] Test Python process failure handling +- [ ] Test model not found error handling +- [ ] Test audio file format validation +- [ ] Test audio quality validation + +### Test Data +- Sample audio files for Vosk testing (clear German speech, noisy audio, non-German speech) +- Sample texts for TTS testing (short, long, with special characters, with German umlauts) +- Sample prompts for Mistral testing (A1, A2, B1 levels) + +--- + +## 🚨 Risks & Mitigations + +### Technical Risks + +| Risk | Likelihood | Impact | Mitigation | Owner | +|------|------------|--------|------------|-------| +| Python-.NET integration failures | High | High | Use Process class with proper error handling, implement process pooling, add timeouts | Backend Dev | +| Vosk model compatibility issues | Medium | High | Test with vosk-model-de-0.22 before implementation, have fallback to vosk-model-small-de-0.15 | Backend Dev | +| Coqui model quality issues | Medium | Medium | Test with sample German text, have alternative TTS service as fallback | Backend Dev | +| Mistral API rate limits | High | Medium | Implement caching (1h TTL), request queue, exponential backoff | Backend Dev | +| Mistral API costs exceed budget | Medium | High | Set budget alerts, implement cost tracking, cache aggressively | Backend Dev | +| AI services slow performance | High | Medium | Implement async processing, use background jobs for batch operations | Backend Dev | +| Audio files too large | Medium | Medium | Compress audio (16kHz, mono), implement streaming for large files | Backend Dev | +| Model files too large for deployment | Medium | Medium | Use Docker volumes, separate storage for models, consider cloud storage | DevOps | +| Memory leaks in Python processes | Medium | High | Implement process lifecycle management, add memory monitoring, use process pooling | Backend Dev | +| Different Python versions cause issues | Medium | Medium | Use Docker to pin Python version, document exact version in README | DevOps | + +### Operational Risks + +| Risk | Likelihood | Impact | Mitigation | Owner | +|------|------------|--------|------------|-------| +| AI service downtime | Medium | High | Implement health checks, circuit breakers, fallback responses | DevOps | +| Model files corrupted | Low | High | Implement checksum validation, store backups, automated recovery | DevOps | +| API key exposure | Medium | High | Use GitHub secrets, Azure Key Vault, never commit to repo | Security | +| Audio storage fills up | Medium | Medium | Implement cleanup job, set size quotas, use cloud storage | DevOps | + +### Business Risks + +| Risk | Likelihood | Impact | Mitigation | Owner | +|------|------------|--------|------------|-------| +| User data privacy concerns | Medium | High | Anonymize audio before processing, document data handling policy, comply with GDPR | Legal | +| AI generates inappropriate content | Low | High | Implement content moderation, add user reporting, use system prompts to prevent | Backend Dev | +| AI services become too expensive | Medium | Medium | Monitor costs, set budget caps, evaluate open-source alternatives | Product | + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required (backend project) + +### Technical Dependencies +- Python 3.8+ +- Vosk Python library +- vosk-model-de-0.22 (German model) +- Coqui TTS Python library +- Coqui German TTS model +- Mistral-Medium API key + +### External Services +| Service | Purpose | Configuration | +|---------|---------|---------------| +| Mistral-Medium API | Text generation (stories, feedback) | API key, endpoint URL | +| Vosk | Speech recognition | Python path, model path | +| Coqui TTS | Text-to-speech | Python path, model name | + +### Blockers +- [ ] Infrastructure Setup must be complete +- [ ] Python environment must be configured +- [ ] AI models must be downloaded +- [ ] Mistral API key must be obtained + +--- + +## 🔧 Technical Deep Dive: Python-.NET Integration + +### Integration Patterns + +#### Option 1: Process.Start (Recommended for MVP) +```csharp +// Simple approach - spawn Python process for each request +public async Task RecognizeSpeechAsync(byte[] audioData) +{ + var tempFile = Path.GetTempFileName() + ".wav"; + await File.WriteAllBytesAsync(tempFile, audioData); + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "python", + Arguments = $"-m vosk.transcribe --model {_modelPath} --input {tempFile}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + // Prevent process from hanging + EnvironmentVariables = new Dictionary + { + ["PYTHONPATH"] = "/path/to/vosk" + } + } + }; + + process.Start(); + + // Read output with timeout + var output = await process.StandardOutput.ReadToEndAsync(); + var error = await process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(); + + if (process.ExitCode != 0) + { + throw new AiServiceException($"Vosk failed: {error}"); + } + + return output.Trim(); +} +``` + +**Pros:** Simple, easy to implement, no additional dependencies +**Cons:** Process startup overhead (~100-500ms per call), resource-intensive + +#### Option 2: Process Pooling (Recommended for Production) +```csharp +// Maintain a pool of persistent Python processes +public class PythonProcessPool : IDisposable +{ + private readonly ConcurrentQueue _pool = new(); + private readonly SemaphoreSlim _semaphore; + private readonly string _pythonPath; + private readonly string _scriptPath; + + public PythonProcessPool(int size, string pythonPath, string scriptPath) + { + _semaphore = new SemaphoreSlim(size); + _pythonPath = pythonPath; + _scriptPath = scriptPath; + + // Pre-warm the pool + for (int i = 0; i < size; i++) + { + _pool.Enqueue(StartProcess()); + } + } + + public async Task ExecuteAsync(string input) + { + await _semaphore.WaitAsync(); + + if (!_pool.TryDequeue(out var process)) + { + process = StartProcess(); + } + + try + { + // Send input to stdin + await process.StandardInput.WriteLineAsync(input); + await process.StandardInput.FlushAsync(); + + // Read response from stdout + var response = await process.StandardOutput.ReadLineAsync(); + + return response; + } + finally + { + _pool.Enqueue(process); + _semaphore.Release(); + } + } + + private Process StartProcess() + { + return new Process + { + StartInfo = new ProcessStartInfo + { + FileName = _pythonPath, + Arguments = _scriptPath, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + } + }.Start(); + } + + public void Dispose() + { + foreach (var process in _pool) + { + try { process.Kill(); } catch { } + process.Dispose(); + } + } +} +``` + +**Pros:** Eliminates process startup overhead, much faster for repeated calls +**Cons:** More complex, need to handle process lifecycle, stdin/stdout parsing + +#### Option 3: gRPC (Best for Production) +- Create Python gRPC server for AI services +- .NET client calls gRPC methods +- Single persistent Python process +- Type-safe, high-performance + +**Pros:** Best performance, type-safe, production-ready +**Cons:** Most complex to set up, requires gRPC knowledge + +### Error Handling Strategy + +```csharp +// Comprehensive error handling for AI services +public async Task ExecuteWithRetryAsync( + Func> action, + string operationName, + int maxRetries = 3, + TimeSpan? timeout = null) +{ + var retryCount = 0; + timeout ??= TimeSpan.FromSeconds(30); + + while (true) + { + try + { + using var cts = new CancellationTokenSource(timeout.Value); + return await action(); + } + catch (OperationCanceledException) when (retryCount < maxRetries) + { + retryCount++; + var delay = TimeSpan.FromSeconds(Math.Pow(2, retryCount)); + _logger.LogWarning( + "{Operation} timed out (attempt {Attempt}), retrying in {Delay}s...", + operationName, retryCount, delay.TotalSeconds); + await Task.Delay(delay); + } + catch (AiServiceException ex) when (IsRetryable(ex) && retryCount < maxRetries) + { + retryCount++; + var delay = TimeSpan.FromSeconds(Math.Pow(2, retryCount)); + _logger.LogWarning(ex, + "{Operation} failed (attempt {Attempt}), retrying in {Delay}s...", + operationName, retryCount, delay.TotalSeconds); + await Task.Delay(delay); + } + catch (Exception ex) + { + _logger.LogError(ex, "{Operation} failed permanently after {Attempts} attempts", + operationName, retryCount + 1); + throw new AiServiceException($"{operationName} failed: {ex.Message}", ex); + } + } + + bool IsRetryable(AiServiceException ex) => + ex.ErrorCode switch + { + AiErrorCode.RateLimited => true, + AiErrorCode.Temporary => true, + AiErrorCode.Timeout => true, + _ => false + }; +} +``` + +### Health Check Implementation + +```csharp +// Health check for AI services +public class AiServicesHealthCheck : IHealthCheck +{ + private readonly IMistralService _mistral; + private readonly IVoskService _vosk; + private readonly ITtsService _tts; + + public async Task CheckHealthAsync( + HealthCheckContext context, + CancellationToken cancellationToken = default) + { + var checks = new Dictionary(); + + // Check Mistral + try + { + await _mistral.TestConnectionAsync(cancellationToken); + checks["Mistral"] = HealthStatus.Healthy; + } + catch (Exception ex) + { + checks["Mistral"] = HealthStatus.Unhealthy; + } + + // Check Vosk + try + { + await _vosk.TestModelAsync(cancellationToken); + checks["Vosk"] = HealthStatus.Healthy; + } + catch (Exception ex) + { + checks["Vosk"] = HealthStatus.Unhealthy; + } + + // Check Coqui TTS + try + { + await _tts.TestModelAsync(cancellationToken); + checks["Coqui TTS"] = HealthStatus.Healthy; + } + catch (Exception ex) + { + checks["Coqui TTS"] = HealthStatus.Unhealthy; + } + + var allHealthy = checks.Values.All(s => s == HealthStatus.Healthy); + var status = allHealthy ? HealthStatus.Healthy : HealthStatus.Unhealthy; + + return new HealthCheckResult( + status, + "AI Services health check", + data: checks); + } +} +``` + +### Audio File Management + +```csharp +// Audio file storage service +public class AudioFileService +{ + private readonly string _basePath; + private readonly ILogger _logger; + + public AudioFileService(IConfiguration config, ILogger logger) + { + _basePath = config["Audio:StoragePath"] ?? "/var/audio"; + _logger = logger; + + Directory.CreateDirectory(_basePath); + } + + public async Task SaveAudioAsync(byte[] audioData, string category, int entityId) + { + // Validate audio data + if (audioData == null || audioData.Length == 0) + throw new ArgumentException("Audio data cannot be empty"); + + if (audioData.Length > 10 * 1024 * 1024) // 10MB limit + throw new ArgumentException("Audio file too large"); + + // Create category directory + var categoryPath = Path.Combine(_basePath, category); + Directory.CreateDirectory(categoryPath); + + // Generate unique filename + var extension = ".wav"; // or detect from data + var filename = $"{entityId}{extension}"; + var fullPath = Path.Combine(categoryPath, filename); + + // Check for existing file + if (File.Exists(fullPath)) + File.Delete(fullPath); + + // Save file + await File.WriteAllBytesAsync(fullPath, audioData); + + // Return relative path + return $"/audio/{category}/{filename}"; + } + + public async Task CleanupOldFilesAsync(TimeSpan olderThan) + { + var cutoff = DateTime.UtcNow - olderThan; + + foreach (var categoryDir in Directory.GetDirectories(_basePath)) + { + foreach (var file in Directory.GetFiles(categoryDir)) + { + var fileInfo = new FileInfo(file); + if (fileInfo.LastWriteTimeUtc < cutoff) + { + try + { + File.Delete(file); + _logger.LogInformation("Deleted old audio file: {File}", file); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to delete audio file: {File}", file); + } + } + } + } + } +} +``` + +### Rate Limiting Implementation + +```csharp +// Rate limiter for AI services +public class AiRateLimiter +{ + private readonly ConcurrentDictionary _limits = new(); + private readonly int _maxRequests; + private readonly TimeSpan _window; + + public AiRateLimiter(int maxRequestsPerWindow, TimeSpan window) + { + _maxRequests = maxRequestsPerWindow; + _window = window; + } + + public bool TryAcquire(string serviceName) + { + var now = DateTime.UtcNow; + + var entry = _limits.GetOrAdd(serviceName, _ => new RateLimitEntry()); + + lock (entry) + { + // Remove old requests + entry.Requests.RemoveAll(r => now - r > _window); + + // Check if limit exceeded + if (entry.Requests.Count >= _maxRequests) + return false; + + // Add new request + entry.Requests.Add(now); + return true; + } + } + + private class RateLimitEntry + { + public List Requests { get; } = new(); + } +} + +// Usage in controller +[HttpPost("recognize")] +public async Task RecognizeSpeech([FromBody] AudioRequest request) +{ + if (!_rateLimiter.TryAcquire("Vosk")) + { + return StatusCode(429, "Too many requests"); + } + + // ... process request +} +``` + +## 📝 Notes & Decisions +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Use Mistral-Medium | Best balance of quality and cost for this use case | +| May 31, 2025 | Use Vosk for speech recognition | Open-source, supports German, self-hostable | +| May 31, 2025 | Use Coqui TTS | Open-source, good quality, supports German | +| May 31, 2025 | Self-host AI services | More control, no external API dependencies (except Mistral) | +| May 31, 2025 | Use Python CLI wrappers | Easier integration with .NET, well-supported libraries | + +### Technical Notes + +#### Vosk Configuration +```json +{ + "Vosk": { + "PythonPath": "/usr/bin/python3", + "ModelPath": "/models/vosk-model-de-0.22", + "SampleRate": 16000 + } +} +``` + +#### Coqui TTS Configuration +```json +{ + "Coqui": { + "PythonPath": "/usr/bin/python3", + "ModelName": "tts_models/de/deu/fairseq/vits", + "AudioOutputFormat": "wav", + "SampleRate": 22050 + } +} +``` + +#### Mistral Configuration +```json +{ + "Mistral": { + "ApiKey": "your-api-key", + "BaseUrl": "https://api.mistral.ai/v1/", + "DefaultModel": "mistral-medium", + "TimeoutSeconds": 30, + "MaxRetries": 3 + } +} +``` + +### Error Handling Strategy +1. **Transient errors**: Retry with exponential backoff +2. **Rate limits**: Return 429 to client, suggest retry +3. **Service unavailable**: Return 503, log error +4. **Invalid response**: Validate output, return meaningful error +5. **Timeout**: Return 504, suggest retry + +### Caching Strategy +- **Mistral responses**: Cache for 1 hour (stories unlikely to change) +- **TTS audio**: Cache files permanently (regenerate only if text changes) +- **Vosk**: No caching (each audio is unique) + +### Gotchas +- ⚠️ Vosk model is ~500MB - ensure enough disk space +- ⚠️ Coqui model is ~1.5GB - ensure enough disk space +- ⚠️ Python processes may have memory leaks - monitor and restart +- ⚠️ AI services may fail silently - implement health checks +- ⚠️ Mistral API has costs - implement budget tracking +- ⚠️ Audio generation can be CPU-intensive - consider separate service +- ⚠️ Different Python versions may have compatibility issues + +### File Storage Structure +``` +/public/ +├── audio/ +│ ├── vocabulary/ # Vocabulary word audio +│ │ └── {id}.wav +│ ├── story/ # Story segment audio +│ │ └── {levelId}-{order}.wav +│ └── quiz/ # Quiz question audio +│ └── {questionId}.wav +└── models/ # AI models + ├── vosk/ + │ └── vosk-model-de-0.22/ + └── coqui/ + └── tts_models/ +``` + +### Performance Considerations +- TTS generation: ~1-2 seconds per sentence +- Speech recognition: ~1-3 seconds per audio clip +- Mistral API: ~2-5 seconds per request +- Consider async/background processing for batch operations + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Feature: [Story Integration](story-integration.md) +- Feature: [Vocabulary System](vocabulary-system.md) +- Feature: [Quiz System](quiz-system.md) +- Reference: [Mistral AI API Docs](https://docs.mistral.ai/) +- Reference: [Vosk Documentation](https://alphacephei.com/vosk/) +- Reference: [Coqui TTS GitHub](https://github.com/coqui-ai/TTS) +- Reference: [vosk-model-de-0.22](https://alphacephei.com/vosk/models) +- Reference: [Coqui German Model](https://github.com/coqui-ai/TTS/wiki/Multilingual-support) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/frontend-ui.md b/docs/features/frontend-ui.md new file mode 100644 index 0000000..e03506d --- /dev/null +++ b/docs/features/frontend-ui.md @@ -0,0 +1,605 @@ +# Feature: Frontend UI & Components + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: High +> **Estimate**: 10-16 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, User Authentication, Lesson Management, Vocabulary System, Quiz System, Story Integration, AI Services + +--- + +## 📌 Overview + +### Purpose +Implement the React + TypeScript frontend application with all UI components, pages, hooks, and services needed for the German learning web app. + +### User Story +As a user, I want a responsive, intuitive, and visually appealing web interface so that I can easily navigate the application, access learning content, and track my progress. + +### Acceptance Criteria +- [ ] React application with TypeScript and Vite +- [ ] Responsive design (mobile-first) +- [ ] All pages and components implemented +- [ ] State management configured +- [ ] API client for backend communication +- [ ] Audio playback and recording functionality +- [ ] Authentication flow integrated + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | React 19 + TypeScript + Vite setup | High | +| FR-002 | Mobile-first responsive design | High | +| FR-003 | Dashboard page with progress | High | +| FR-004 | Lesson pages with all tabs | High | +| FR-005 | Quiz pages with all question types | High | +| FR-006 | Story pages with audio | High | +| FR-007 | Practice pages for speaking/writing | High | +| FR-008 | Profile page | Medium | +| FR-009 | Login/Registration pages | High | +| FR-010 | Audio player component | High | +| FR-011 | Audio recorder component | High | +| FR-012 | API client service | High | +| FR-013 | Authentication context | High | +| FR-014 | Form handling and validation | Medium | +| FR-015 | Error handling and notifications | Medium | + +### Non-Functional Requirements +- Performance: Pages load in < 2 seconds +- Accessibility: WCAG 2.1 AA compliance +- Compatibility: Chrome, Firefox, Safari, Edge (latest 2 versions) +- SEO: Basic SEO metadata +- Offline: Basic offline support (cache API responses) + +--- + +## 🏗️ Technical Design + +### Architecture Overview +``` +german-app-frontend/ +├── public/ # Static files +│ └── audio/ # Audio files (served from backend) +├── src/ +│ ├── components/ # Reusable UI components +│ │ ├── Dashboard/ # Progress bar, next lesson button +│ │ ├── Lesson/ # Vocabulary, Grammar, Story, Quiz tabs +│ │ ├── Quiz/ # Question type components +│ │ ├── Story/ # Story segment components +│ │ ├── Shared/ # Reusable UI elements +│ │ └── Layout/ # Header, Footer, Navigation +│ │ +│ ├── pages/ # Page components +│ │ ├── DashboardPage.tsx +│ │ ├── LessonPage.tsx +│ │ ├── QuizPage.tsx +│ │ ├── StoryPage.tsx +│ │ ├── PracticePage.tsx +│ │ ├── ProfilePage.tsx +│ │ ├── LoginPage.tsx +│ │ └── RegisterPage.tsx +│ │ +│ ├── hooks/ # Custom React hooks +│ │ ├── useApi.ts # API client hook +│ │ ├── useAuth.ts # Authentication hook +│ │ ├── useAudio.ts # Audio playback hook +│ │ ├── useRecorder.ts # Audio recording hook +│ │ └── useLesson.ts # Lesson data hook +│ │ +│ ├── services/ # API and business logic services +│ │ ├── api.ts # Base API client +│ │ ├── authService.ts # Authentication service +│ │ ├── lessonService.ts # Lesson service +│ │ ├── quizService.ts # Quiz service +│ │ ├── speechService.ts # Speech recognition service +│ │ └── ttsService.ts # TTS service +│ │ +│ ├── stores/ # State management +│ │ └── authStore.ts # Authentication state +│ │ +│ ├── types/ # TypeScript type definitions +│ │ └── index.ts # API types, component props +│ │ +│ ├── styles/ # Global styles +│ │ ├── tailwind.css # Tailwind CSS +│ │ └── global.css # Global styles +│ │ +│ ├── utils/ # Utility functions +│ │ ├── constants.ts # Application constants +│ │ └── helpers.ts # Helper functions +│ │ +│ ├── App.tsx # Main application component +│ └── main.tsx # Application entry point +│ +├── package.json +├── vite.config.ts +└── tsconfig.json +``` + +### Component Hierarchy +``` +App +├── AuthContext +│ └── AuthProvider +├── Router +│ ├── PublicRoute +│ │ ├── LoginPage +│ │ ├── RegisterPage +│ │ └── ... +│ └── PrivateRoute +│ ├── DashboardPage +│ ├── LessonPage +│ ├── QuizPage +│ ├── StoryPage +│ ├── PracticePage +│ └── ProfilePage +└── Layout + ├── Header + └── Footer +``` + +### Data Flow +``` +1. User navigates to a page +2. Page component uses custom hooks to fetch data +3. Hooks call services which use API client +4. API client makes HTTP requests to backend +5. Backend returns data +6. Hooks transform and provide data to components +7. Components render UI with data +8. User interactions trigger service calls +9. Services update backend via API +10. UI updates to reflect changes +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Project Setup (2-4 hours) +- [ ] Initialize Vite + React + TypeScript project +- [ ] Install dependencies (React Router, Tailwind CSS, Axios, etc.) +- [ ] Configure Vite for development and production +- [ ] Set up TypeScript configuration +- [ ] Configure Tailwind CSS +- [ ] Set up folder structure +- [ ] Configure environment variables +- [ ] Set up base HTML template + +### Phase 2: Core Infrastructure (2-3 hours) +- [ ] Create API client with Axios +- [ ] Set up error handling and interceptors +- [ ] Create authentication context +- [ ] Implement protected route wrapper +- [ ] Set up state management (Zustand or Redux) +- [ ] Create type definitions for API responses +- [ ] Configure routing with React Router + +### Phase 3: Shared Components (2-3 hours) +- [ ] Create Layout components (Header, Footer) +- [ ] Create Button, Input, Card components +- [ ] Create LoadingSpinner, ErrorMessage components +- [ ] Create AudioPlayer component +- [ ] Create Recorder component +- [ ] Create Modal component +- [ ] Create Notification system + +### Phase 4: Authentication Pages (2 hours) +- [ ] Create LoginPage +- [ ] Create RegisterPage +- [ ] Create ProfilePage +- [ ] Implement form validation +- [ ] Integrate with AuthService +- [ ] Handle authentication state + +### Phase 5: Dashboard Page (2 hours) +- [ ] Create DashboardPage +- [ ] Create ProgressBar component +- [ ] Create NextLessonButton component +- [ ] Create StoryProgress component +- [ ] Create Gamification components (PointsDisplay, StreakDisplay, BadgeList) +- [ ] Integrate with data hooks + +### Phase 6: Lesson Pages (3-4 hours) +- [ ] Create LessonPage with tab navigation +- [ ] Create VocabularyTab component +- [ ] Create GrammarTab component +- [ ] Create StoryTab component +- [ ] Create ReadingTab component +- [ ] Create ListeningExercise component +- [ ] Create SpeakingExercise component +- [ ] Create WritingExercise component +- [ ] Create QuizTab component +- [ ] Integrate AudioPlayer and Recorder + +### Phase 7: Quiz Pages (2 hours) +- [ ] Create QuizPage +- [ ] Create McqQuestion component +- [ ] Create FillInBlank component +- [ ] Create ListeningQuestion component +- [ ] Create MatchingQuestion component +- [ ] Create QuizResults component +- [ ] Implement quiz submission +- [ ] Display results with feedback + +### Phase 8: Story Pages (2 hours) +- [ ] Create StoryPage +- [ ] Create StorySegment component +- [ ] Create StoryPlayer component +- [ ] Implement word click-to-translate +- [ ] Add story navigation +- [ ] Display story progress + +### Phase 9: Practice Pages (2 hours) +- [ ] Create PracticePage +- [ ] Create SpeakingPractice component +- [ ] Create WritingPractice component +- [ ] Integrate speech recognition +- [ ] Integrate writing feedback +- [ ] Display practice history + +### Phase 10: Polish & Testing (2 hours) +- [ ] Add loading states +- [ ] Add error boundaries +- [ ] Implement form validation +- [ ] Add responsive design tweaks +- [ ] Test on mobile devices +- [ ] Performance optimization +- [ ] Accessibility audit + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Project Setup | - | ⏳ | +| Core Infrastructure | - | ⏳ | +| Shared Components | - | ⏳ | +| Authentication Pages | - | ⏳ | +| Dashboard Page | - | ⏳ | +| Lesson Pages | - | ⏳ | +| Quiz Pages | - | ⏳ | +| Story Pages | - | ⏳ | +| Practice Pages | - | ⏳ | +| Polish & Testing | - | ⏳ | + +--- + +## ✅ Tasks + +### Project Setup +- [ ] Initialize Vite project +- [ ] Install React and React DOM +- [ ] Install TypeScript +- [ ] Install React Router +- [ ] Install Tailwind CSS +- [ ] Install Axios +- [ ] Install Zustand (or Redux Toolkit) +- [ ] Install @testing-library/react +- [ ] Install other dependencies +- [ ] Configure Vite +- [ ] Configure TypeScript +- [ ] Configure Tailwind +- [ ] Set up folder structure +- [ ] Create base files + +### Core Infrastructure +- [ ] Create src/services/api.ts +- [ ] Create src/contexts/AuthContext.tsx +- [ ] Create src/components/Layout/PrivateRoute.tsx +- [ ] Create src/components/Layout/PublicRoute.tsx +- [ ] Create src/types/index.ts +- [ ] Set up React Router in main.tsx +- [ ] Configure Axios interceptors + +### Shared Components +- [ ] Create src/components/Layout/Header.tsx +- [ ] Create src/components/Layout/Footer.tsx +- [ ] Create src/components/Shared/Button.tsx +- [ ] Create src/components/Shared/Input.tsx +- [ ] Create src/components/Shared/Card.tsx +- [ ] Create src/components/Shared/LoadingSpinner.tsx +- [ ] Create src/components/Shared/ErrorMessage.tsx +- [ ] Create src/components/Shared/AudioPlayer.tsx +- [ ] Create src/components/Shared/Recorder.tsx +- [ ] Create src/components/Shared/Modal.tsx +- [ ] Create src/components/Shared/Notification.tsx + +### Authentication +- [ ] Create src/pages/LoginPage.tsx +- [ ] Create src/pages/RegisterPage.tsx +- [ ] Create src/pages/ProfilePage.tsx +- [ ] Create src/hooks/useAuth.ts +- [ ] Create src/services/authService.ts +- [ ] Implement form validation +- [ ] Integrate with AuthContext + +### Dashboard +- [ ] Create src/pages/DashboardPage.tsx +- [ ] Create src/components/Dashboard/ProgressBar.tsx +- [ ] Create src/components/Dashboard/NextLessonButton.tsx +- [ ] Create src/components/Dashboard/StoryProgress.tsx +- [ ] Create src/components/Gamification/PointsDisplay.tsx +- [ ] Create src/components/Gamification/StreakDisplay.tsx +- [ ] Create src/components/Gamification/BadgeDisplay.tsx +- [ ] Create src/hooks/useDashboard.ts + +### Lesson Components +- [ ] Create src/pages/LessonPage.tsx +- [ ] Create src/components/Lesson/VocabularyTab.tsx +- [ ] Create src/components/Lesson/GrammarTab.tsx +- [ ] Create src/components/Lesson/StoryTab.tsx +- [ ] Create src/components/Lesson/ReadingTab.tsx +- [ ] Create src/components/Lesson/ListeningExercise.tsx +- [ ] Create src/components/Lesson/SpeakingExercise.tsx +- [ ] Create src/components/Lesson/WritingExercise.tsx +- [ ] Create src/components/Lesson/QuizTab.tsx +- [ ] Create src/hooks/useLesson.ts + +### Quiz Components +- [ ] Create src/pages/QuizPage.tsx +- [ ] Create src/components/Quiz/McqQuestion.tsx +- [ ] Create src/components/Quiz/FillInBlank.tsx +- [ ] Create src/components/Quiz/ListeningQuestion.tsx +- [ ] Create src/components/Quiz/MatchingQuestion.tsx +- [ ] Create src/components/Quiz/QuizResults.tsx +- [ ] Create src/hooks/useQuiz.ts +- [ ] Create src/services/quizService.ts + +### Story Components +- [ ] Create src/pages/StoryPage.tsx +- [ ] Create src/components/Story/StorySegment.tsx +- [ ] Create src/components/Story/StoryPlayer.tsx +- [ ] Create src/hooks/useStory.ts +- [ ] Create src/services/storyService.ts +- [ ] Implement word translation on click + +### Practice Components +- [ ] Create src/pages/PracticePage.tsx +- [ ] Create src/components/Practice/SpeakingPractice.tsx +- [ ] Create src/components/Practice/WritingPractice.tsx +- [ ] Create src/hooks/usePractice.ts +- [ ] Create src/services/speechService.ts +- [ ] Create src/services/ttsService.ts + +### Polish & Testing +- [ ] Add loading states to all components +- [ ] Add error boundaries +- [ ] Implement form validation +- [ ] Add responsive design +- [ ] Test on mobile devices +- [ ] Optimize bundle size +- [ ] Run accessibility audit + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Backend must be ready +- [User Authentication](user-authentication.md) - Auth flow must be implemented +- [Lesson Management](lesson-management.md) - Lesson data must be available +- [Vocabulary System](vocabulary-system.md) - Vocabulary data must be available +- [Quiz System](quiz-system.md) - Quiz data must be available +- [Story Integration](story-integration.md) - Story data must be available +- [AI Services](ai-services.md) - AI services must be available +- [Gamification](gamification.md) - Gamification data must be available + +### Technical Dependencies +- Node.js 18+ +- npm or yarn +- Vite +- React 19 +- TypeScript +- Tailwind CSS +- React Router +- Axios +- Zustand or Redux + +### Blockers +- [ ] Backend API endpoints must be implemented +- [ ] AI services must be configured +- [ ] Database must be set up + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Frontend-Specific Criteria +- [ ] React application builds successfully +- [ ] All pages render without errors +- [ ] All components render without errors +- [ ] All hooks work correctly +- [ ] All services work correctly +- [ ] API calls return expected data +- [ ] Authentication flow works end-to-end +- [ ] Audio playback works on all supported browsers +- [ ] Audio recording works on all supported browsers +- [ ] Responsive design works on mobile, tablet, and desktop +- [ ] Form validation works correctly +- [ ] Error handling displays user-friendly messages +- [ ] Loading states are displayed appropriately +- [ ] Accessibility meets WCAG 2.1 AA standards + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Frontend-Specific Tests + +#### Component Tests +- [ ] All components render without errors +- [ ] All components render with required props +- [ ] All components render with optional props +- [ ] All components handle user interactions correctly +- [ ] All components display loading states +- [ ] All components display error states +- [ ] All components are accessible (keyboard, screen reader) +- [ ] All components are responsive + +#### Page Tests +- [ ] Dashboard page loads and displays data +- [ ] Lesson page loads and displays lesson content +- [ ] Lesson page tabs work correctly +- [ ] Quiz page loads and displays questions +- [ ] Quiz page submission works +- [ ] Story page loads and displays segments +- [ ] Story page audio works +- [ ] Practice page loads and displays exercises +- [ ] Practice page speech recognition works +- [ ] Practice page writing feedback works +- [ ] Login page loads and handles submission +- [ ] Register page loads and handles submission +- [ ] Profile page loads and displays user data + +#### Service Tests +- [ ] API client makes correct requests +- [ ] API client handles errors correctly +- [ ] API client includes auth headers +- [ ] Auth service handles login/logout +- [ ] Lesson service returns correct data +- [ ] Quiz service handles submission +- [ ] Speech service handles recording/transcription +- [ ] TTS service handles audio generation + +#### Integration Tests +- [ ] Auth context provides user state +- [ ] Protected routes redirect correctly +- [ ] Public routes are accessible +- [ ] AudioPlayer component plays audio +- [ ] Recorder component captures audio +- [ ] Modal component opens/closes +- [ ] Notification system displays messages + +#### E2E Tests (Critical Journeys) +- [ ] User registration → login → view dashboard +- [ ] User navigation to lesson → complete activities → take quiz +- [ ] User navigation to story → listen to audio +- [ ] User completes lesson → quiz → progress updated +- [ ] User earns badge → badge displayed on profile + +#### Browser Compatibility Tests +- [ ] All functionality works on Chrome (latest 2 versions) +- [ ] All functionality works on Firefox (latest 2 versions) +- [ ] All functionality works on Safari (latest 2 versions) +- [ ] All functionality works on Edge (latest 2 versions) +- [ ] Audio recording works on mobile Chrome +- [ ] Audio recording works on mobile Safari +- [ ] Responsive design works on all screen sizes + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Vite over Create React App | Faster, more modern, better DX | +| May 31, 2025 | React 19 | Latest stable version | +| May 31, 2025 | TypeScript | Type safety, better developer experience | +| May 31, 2025 | Tailwind CSS | Rapid styling, consistent design | +| May 31, 2025 | Zustand for state | Simpler than Redux, good TypeScript support | +| May 31, 2025 | Axios for HTTP | Better error handling than fetch | +| May 31, 2025 | Mobile-first | Most users will be on mobile | + +### Technical Notes +- Use functional components with hooks +- Use TypeScript interfaces for all props +- Use async/await for API calls +- Handle loading and error states gracefully +- Use environment variables for API base URL +- Audio files served from backend /public/audio/ +- Consider lazy loading for large components + +### Component Library Considerations +For MVP, use custom components. Consider these libraries for future: +- **UI Components**: shadcn/ui, Radix UI, or Material UI +- **Forms**: React Hook Form + Zod +- **Icons**: Lucide React or Heroicons +- **Animations**: Framer Motion +- **Charts**: Recharts or Chart.js + +### Audio Handling +- Use HTML5 Audio element for playback +- Use MediaRecorder API for recording +- Handle microphone permission errors +- Support Web Audio API for advanced features +- Consider audio compression for upload + +### Performance Optimization +- Code splitting with React.lazy +- Image optimization +- Bundle analysis with rollup-plugin-visualizer +- Service worker for caching (workbox) +- Preload important resources + +### Gotchas +- ⚠️ MediaRecorder API may not work on some mobile browsers +- ⚠️ Audio playback may be blocked without user interaction +- ⚠️ Safari has different audio format support +- ⚠️ Mobile keyboards may cover input fields +- ⚠️ Touch targets must be large enough for mobile +- ⚠️ CORS issues with audio files from different domains + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Frontend Structure](../architecture/frontend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Feature: [Infrastructure Setup](infrastructure-setup.md) +- Feature: [User Authentication](user-authentication.md) +- Feature: [Lesson Management](lesson-management.md) +- Feature: [Vocabulary System](vocabulary-system.md) +- Feature: [Quiz System](quiz-system.md) +- Feature: [Story Integration](story-integration.md) +- Feature: [AI Services](ai-services.md) +- Feature: [Gamification](gamification.md) +- Reference: [Vite Docs](https://vitejs.dev/) +- Reference: [React Docs](https://react.dev/) +- Reference: [TypeScript Docs](https://www.typescriptlang.org/docs/) +- Reference: [Tailwind CSS Docs](https://tailwindcss.com/docs) +- Reference: [React Router Docs](https://reactrouter.com/) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/gamification.md b/docs/features/gamification.md new file mode 100644 index 0000000..d4b132b --- /dev/null +++ b/docs/features/gamification.md @@ -0,0 +1,416 @@ +# Feature: Gamification System + +> **Status**: ⏳ Planned +> **Priority**: Medium +> **Complexity**: Medium +> **Estimate**: 6-8 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, User Authentication, Lesson Management, Quiz System + +--- + +## 📌 Overview + +### Purpose +Implement gamification features (points, badges, streaks) to motivate users and provide a sense of achievement as they progress through the German learning journey. + +### User Story +As a learner, I want to earn points, badges, and maintain streaks for my learning activities so that I stay motivated and can see my progress over time. + +### Acceptance Criteria +- [ ] Users earn points for completing lessons and passing quizzes +- [ ] Users earn badges for specific achievements +- [ ] Daily streaks are tracked and displayed +- [ ] Users can view their points, badges, and streak on dashboard +- [ ] Leaderboard shows top users (optional) +- [ ] Achievement notifications are displayed + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | Earn points for completing lessons | High | +| FR-002 | Earn points for passing quizzes | High | +| FR-003 | Define and award badges for achievements | High | +| FR-004 | Track daily learning streaks | High | +| FR-005 | Display points, badges, streak on dashboard | High | +| FR-006 | Show badge details and unlock criteria | Medium | +| FR-007 | Implement leaderboard (optional) | Low | +| FR-008 | Send achievement notifications | Low | + +### Non-Functional Requirements +- Performance: Gamification updates < 100ms +- Scalability: System should handle thousands of users +- Security: Prevent points/badges exploitation + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: GamificationController, GamificationService, PointsService, BadgeService, StreakService +- **Database**: Badges, UserBadges tables (from schema), Users table (points and streak fields) +- **Models**: Badge, UserBadge, PointsTransaction, Achievement +- **Frontend**: Dashboard components, BadgeDisplay, PointsDisplay, StreakDisplay + +### Data Flow +``` +1. User completes a lesson or passes a quiz +2. Backend calculates points earned +3. Backend updates User.TotalPoints +4. Backend checks if any badges are earned +5. If badge earned, create UserBadge record +6. Backend updates daily streak if applicable +7. Frontend displays updated gamification data +``` + +### Points System +| Action | Points | Notes | +|--------|--------|-------| +| Complete Lesson | 10 | Awarded once per lesson | +| Pass Quiz (first try) | 20 | Bonus for first-time pass | +| Pass Quiz (subsequent) | 10 | Still earns points | +| Perfect Quiz Score | +5 | Bonus for 100% | +| Daily Login | 5 | Encourages daily use | + +### Badge System +| Badge Name | Criteria | Description | +|------------|----------|-------------| +| First Lesson | Complete 1 lesson | Getting Started | +| A1 Complete | Complete all A1 lessons | A1 Master | +| Perfect Score | Pass 5 quizzes with 100% | Perfect Student | +| Streak 7 | 7-day learning streak | Weekly Warrior | +| Streak 30 | 30-day learning streak | Monthly Marvel | +| Early Bird | Complete lesson before 9 AM | Morning Learner | +| Night Owl | Complete lesson after 9 PM | Late Night Learner | +| Vocabulary Master | Learn 100 words | Word Champion | +| Grammar Guru | Pass 10 grammar quizzes | Grammar Expert | + +### Streak System +- Streak increments by 1 for each day with at least one completed activity +- Streak resets if user has no activity for 24 hours +- Last activity timestamp stored in User table + +### API Endpoints +| Endpoint | Method | Description | Auth Required | +|----------|--------|-------------|----------------| +| `/api/gamification/me` | GET | Get current user's gamification data | Yes | +| `/api/gamification/points` | GET | Get user's points history | Yes | +| `/api/gamification/badges` | GET | List all badges | Yes | +| `/api/gamification/badges/earned` | GET | List user's earned badges | Yes | +| `/api/gamification/badges/{id}` | GET | Get badge details | Yes | +| `/api/gamification/leaderboard` | GET | Get top users by points | Yes | +| `/api/gamification/streak` | GET | Get streak information | Yes | + +### Database Schema (from application-plan.md) +```sql +-- Badges table +CREATE TABLE Badges ( + Id SERIAL PRIMARY KEY, + Name VARCHAR(50) NOT NULL, + Description TEXT, + ImageUrl VARCHAR(255), + CriteriaType VARCHAR(50) NOT NULL, -- e.g., 'lessons_completed', 'streak_days' + CriteriaValue INT NOT NULL, -- e.g., 10 for 10 lessons completed + PointsReward INT DEFAULT 0, + UNIQUE(Name) +); + +-- User Badges table +CREATE TABLE UserBadges ( + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + BadgeId INT REFERENCES Badges(Id) ON DELETE CASCADE, + EarnedDate TIMESTAMP DEFAULT NOW(), + PRIMARY KEY (UserId, BadgeId) +); + +-- Users table (existing fields) +ALTER TABLE Users ADD COLUMN TotalPoints INT DEFAULT 0; +ALTER TABLE Users ADD COLUMN Streak INT DEFAULT 0; +ALTER TABLE Users ADD COLUMN LastActivityDate TIMESTAMP; +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Database & Models (2 hours) +- [ ] Update Users table with gamification fields +- [ ] Create Badge entity +- [ ] Create UserBadge entity +- [ ] Create PointsTransaction entity (optional for history) +- [ ] Create DTOs for gamification data +- [ ] Create migrations +- [ ] Seed initial badges + +### Phase 2: Points Service (1-2 hours) +- [ ] Create PointsService +- [ ] Implement points calculation logic +- [ ] Create points awarding mechanism +- [ ] Implement points history tracking +- [ ] Create PointsTransaction repository + +### Phase 3: Badge Service (2 hours) +- [ ] Create BadgeService +- [ ] Implement badge criteria checking +- [ ] Create badge awarding mechanism +- [ ] Implement badge checking on user actions +- [ ] Create badge details endpoint + +### Phase 4: Streak Service (1-2 hours) +- [ ] Create StreakService +- [ ] Implement streak calculation logic +- [ ] Create streak update mechanism +- [ ] Handle streak reset logic +- [ ] Create streak endpoint + +### Phase 5: Gamification Controller (1 hour) +- [ ] Create GamificationController +- [ ] Implement all endpoints +- [ ] Add authorization +- [ ] Add validation +- [ ] Write tests + +### Phase 6: Frontend Integration (2 hours) +- [ ] Create Dashboard gamification section +- [ ] Create BadgeDisplay component +- [ ] Create PointsDisplay component +- [ ] Create StreakDisplay component +- [ ] Create Leaderboard component (optional) +- [ ] Add achievement notifications + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Database & Models | - | ⏳ | +| Points Service | - | ⏳ | +| Badge Service | - | ⏳ | +| Streak Service | - | ⏳ | +| Controller | - | ⏳ | +| Frontend Integration | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Update Domain/Entities/User.cs with gamification fields +- [ ] Create Domain/Entities/Badge.cs +- [ ] Create Domain/Entities/UserBadge.cs +- [ ] Create Application/DTOs/GamificationDto.cs +- [ ] Create Application/DTOs/BadgeDto.cs +- [ ] Create Application/DTOs/UserBadgeDto.cs +- [ ] Create Domain/Interfaces/IBadgeRepository.cs +- [ ] Create Domain/Interfaces/IUserBadgeRepository.cs +- [ ] Create Infrastructure/Data/Repositories/BadgeRepository.cs +- [ ] Create Infrastructure/Data/Repositories/UserBadgeRepository.cs +- [ ] Create Application/Services/PointsService.cs +- [ ] Create Application/Services/BadgeService.cs +- [ ] Create Application/Services/StreakService.cs +- [ ] Create Application/Services/GamificationService.cs +- [ ] Create Presentation/Controllers/GamificationController.cs +- [ ] Register services in Program.cs +- [ ] Write unit tests +- [ ] Write integration tests + +### Database +- [ ] Create migration for gamification fields in Users +- [ ] Create migration for Badges table +- [ ] Create migration for UserBadges table +- [ ] Seed initial badges +- [ ] Apply migrations + +### Integration +- [ ] Integrate PointsService with LessonService +- [ ] Integrate PointsService with QuizService +- [ ] Integrate BadgeService with user actions +- [ ] Integrate StreakService with user activity +- [ ] Add gamification updates to relevant endpoints + +### Frontend +- [ ] Update DashboardPage with gamification section +- [ ] Create components/Gamification/PointsDisplay.tsx +- [ ] Create components/Gamification/StreakDisplay.tsx +- [ ] Create components/Gamification/BadgeDisplay.tsx +- [ ] Create components/Gamification/BadgeList.tsx +- [ ] Create components/Gamification/Leaderboard.tsx (optional) +- [ ] Create hooks/useGamification.ts +- [ ] Add achievement notification system + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required +- [User Authentication](user-authentication.md) - Required (for user-specific data) +- [Lesson Management](lesson-management.md) - Required (points for lessons) +- [Quiz System](quiz-system.md) - Required (points for quizzes) + +### Technical Dependencies +- Entity Framework Core +- AutoMapper (optional) + +### Blockers +- [ ] Infrastructure Setup must be complete +- [ ] User Authentication must be complete +- [ ] Lesson Management must be complete +- [ ] Quiz System must be complete + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Gamification-Specific Criteria +- [ ] Points are earned for all defined actions +- [ ] Points are displayed correctly on dashboard +- [ ] All badges are defined and seeded +- [ ] Badges are awarded automatically when criteria met +- [ ] Earned badges are displayed on user profile +- [ ] Daily streaks are tracked correctly +- [ ] Streak is displayed on dashboard +- [ ] Leaderboard (if implemented) shows correct data +- [ ] Achievement notifications are displayed +- [ ] Points and badges cannot be manipulated client-side + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Gamification-Specific Tests + +#### Backend Tests +- [ ] Points awarded for lesson completion → correct amount +- [ ] Points awarded for quiz pass (first try) → correct amount +- [ ] Points awarded for quiz pass (subsequent) → correct amount +- [ ] Points awarded for perfect quiz → correct amount + bonus +- [ ] Points NOT awarded for quiz fail → no change +- [ ] Badge awarded when criteria met → badge created +- [ ] Badge NOT awarded when criteria not met → no change +- [ ] Multiple badges can be earned simultaneously → all awarded +- [ ] Streak increments on daily activity → +1 +- [ ] Streak resets after inactivity → 0 +- [ ] Streak maintains across days → correct count +- [ ] Leaderboard returns top N users → correct ordering + +#### Business Logic Tests +- [ ] Points calculation with various actions → correct total +- [ ] Badge criteria evaluation → correct determination +- [ ] Streak calculation across time zones → consistent +- [ ] Concurrent points awards → no race conditions +- [ ] Badge awarding is idempotent → only awarded once + +#### Security Tests +- [ ] Points cannot be awarded client-side → server-only +- [ ] Badges cannot be awarded client-side → server-only +- [ ] Streak cannot be manipulated client-side → server-only +- [ ] Leaderboard doesn't expose sensitive data → verified + +#### Frontend Tests +- [ ] Points display updates after earning → correct +- [ ] Badge display shows earned badges → correct +- [ ] Badge details modal shows correct info → verified +- [ ] Streak display updates daily → correct +- [ ] Achievement notification appears when earned → verified +- [ ] Leaderboard displays correctly → verified + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Points for lesson completion | Encourages progress through curriculum | +| May 31, 2025 | Badge system | Provides goals and achievements | +| May 31, 2025 | Daily streaks | Encourages consistent daily use | +| May 31, 2025 | Server-authoritative | Prevents client-side manipulation of points | +| May 31, 2025 | No negative points | Positive reinforcement only | + +### Technical Notes +- Points should be awarded server-side only, never client-side +- Badge criteria should be checked on relevant user actions +- Streak calculation: Compare LastActivityDate with previous day +- Consider time zones for daily streaks (use UTC for consistency) +- Badge images can be stored as URLs or base64 encoded + +### Points Calculation Examples +``` +Lesson Completion: +- Base: 10 points +- First-time: +10 = 20 points total (with Quiz bonus) + +Quiz Pass: +- First try: 20 points +- Subsequent: 10 points +- Perfect score: +5 bonus +``` + +### Badge Criteria Types +- `lessons_completed`: Number of lessons completed +- `quizzes_passed`: Number of quizzes passed +- `streak_days`: Current streak in days +- `perfect_quizzes`: Number of perfect scores +- `vocabulary_learned`: Number of vocabulary words mastered +- `levels_completed`: Number of CEFR levels completed + +### Gotchas +- ⚠️ Points can get large - use INT or BIGINT +- ⚠️ Badge criteria may need adjustment based on testing +- ⚠️ Streak calculation must handle time zones correctly +- ⚠️ Users may try to game the system - validate all inputs +- ⚠️ Badge images may have copyright issues - use original or licensed images + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Feature: [User Authentication](user-authentication.md) +- Feature: [Lesson Management](lesson-management.md) +- Feature: [Quiz System](quiz-system.md) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/infrastructure-setup.md b/docs/features/infrastructure-setup.md new file mode 100644 index 0000000..5dc7aec --- /dev/null +++ b/docs/features/infrastructure-setup.md @@ -0,0 +1,273 @@ +# Feature: Infrastructure Setup + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: Medium +> **Estimate**: 10-14 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **Related Features**: All other features depend on this + +--- + +## 📌 Overview + +### Purpose +Establish the foundational infrastructure for the DeutschLernen application, including backend project, database, and core dependencies. + +### User Story +As a developer, I want to have a working backend and database setup so that I can begin implementing application features. + +### Acceptance Criteria +- [ ] .NET 9.0 backend project is created and builds successfully +- [ ] PostgreSQL database is configured and accessible +- [ ] Docker setup is ready for deployment +- [ ] CI/CD pipeline is configured +- [ ] Development environment is reproducible + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | .NET 9.0 backend with ASP.NET Core Web API | High | +| FR-002 | PostgreSQL database with connection string configuration | High | +| FR-003 | Docker containers for backend and database | High | +| FR-004 | GitHub Actions CI/CD pipeline | Medium | +| FR-005 | Environment configuration (development, staging, production) | Medium | + +### Non-Functional Requirements +- Performance: Backend should start in < 5 seconds +- Security: Database credentials stored securely (not in code) +- Compatibility: Works on Linux, Windows, and macOS +- Scalability: Infrastructure should support horizontal scaling + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: ASP.NET Core Web API project +- **Database**: PostgreSQL 15+ +- **Infrastructure**: Docker, Docker Compose +- **CI/CD**: GitHub Actions + +### Architecture Diagram +``` +┌─────────────────────────────────────────────────────────┐ +│ Infrastructure │ +├─────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Backend │ │ Database │ │ Frontend │ │ +│ │ (.NET 9.0) │◄──►│ PostgreSQL │ │ (React) │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ▲ ▲ ▲ │ +│ │ │ │ │ +│ ┌────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ │ +│ │ Docker │ │ Docker │ │ Docker │ │ +│ │ Container│ │ Container │ │ Container │ │ +│ └──────────┘ └───────────┘ └───────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### Data Flow +1. Developer runs `docker-compose up` +2. PostgreSQL container starts with initialized database +3. Backend container starts and connects to database +4. Frontend container starts and connects to backend API +5. Application is accessible at `http://localhost:3000` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Backend Project Setup (2-4 hours) +- [ ] Create GermanApp .NET 9.0 Web API project +- [ ] Configure appsettings.json with multiple environments +- [ ] Set up Health Checks endpoint +- [ ] Configure CORS for frontend +- [ ] Set up OpenAPI/Swagger documentation +- [ ] Configure logging (Serilog or built-in) +- [ ] Create base response models and error handling middleware + +### Phase 2: Database Setup (1-2 hours) +- [ ] Design and create initial database schema +- [ ] Configure Entity Framework Core with PostgreSQL +- [ ] Set up database migrations +- [ ] Create seed data scripts +- [ ] Configure connection strings for different environments + +### Phase 3: Docker Configuration (2-4 hours) +- [ ] Create Dockerfile for backend +- [ ] Create Dockerfile for frontend +- [ ] Create docker-compose.yml with all services +- [ ] Configure Docker volumes for persistent data +- [ ] Set up environment variables in Docker +- [ ] Test Docker build and run + +### Phase 4: CI/CD Pipeline (2-4 hours) +- [ ] Create GitHub Actions workflow for backend +- [ ] Configure build, test, and deploy steps +- [ ] Set up environment secrets +- [ ] Configure branch protection rules +- [ ] Test CI/CD pipeline + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Backend Project Created | - | ⏳ | +| Database Configured | - | ⏳ | +| Docker Setup Complete | - | ⏳ | +| CI/CD Pipeline Working | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Initialize .NET 9.0 Web API project +- [ ] Configure Program.cs with proper middleware +- [ ] Set up appsettings.Development.json, appsettings.Staging.json, appsettings.Production.json +- [ ] Create HealthChecks endpoint +- [ ] Configure Swagger/OpenAPI +- [ ] Set up CORS policy +- [ ] Configure logging +- [ ] Create error handling middleware +- [ ] Create base response wrappers + +### Database +- [ ] Install PostgreSQL locally for development +- [ ] Create initial database schema +- [ ] Configure EF Core DbContext +- [ ] Create first migration +- [ ] Apply migration to database +- [ ] Create seed data for initial testing + +### Docker +- [ ] Create backend Dockerfile +- [ ] Create frontend Dockerfile +- [ ] Create docker-compose.yml +- [ ] Configure Docker volumes +- [ ] Set up Docker .env file +- [ ] Test Docker containers + +### CI/CD +- [ ] Create .github/workflows/ directory +- [ ] Create backend CI workflow +- [ ] Create frontend CI workflow +- [ ] Configure deployment workflow +- [ ] Set up GitHub secrets +- [ ] Test workflows + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- None - This is the foundational feature + +### Technical Dependencies +- .NET 9.0 SDK +- PostgreSQL 15+ +- Docker & Docker Compose +- GitHub account with repository access + +### Blockers +- [ ] None identified + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Infrastructure-Specific Criteria +- [ ] .NET 9.0 backend project builds successfully +- [ ] PostgreSQL database is accessible and configured +- [ ] Docker containers build and run without errors +- [ ] CI/CD pipeline runs successfully on push +- [ ] Development environment is reproducible by new team members +- [ ] Health checks pass for all services +- [ ] Database migrations can be applied cleanly + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Infrastructure-Specific Tests +- [ ] Verify .NET 9.0 project structure +- [ ] Test database connection and queries +- [ ] Test Docker build process +- [ ] Test Docker Compose startup +- [ ] Test CI/CD pipeline execution +- [ ] Test health check endpoints +- [ ] Test database migration process +- [ ] Test backup and restore procedures + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Use PostgreSQL over SQLite | Better for production, supports more features, widely used | +| May 31, 2025 | Use Docker Compose | Simplified local development setup | +| May 31, 2025 | GitHub Actions for CI/CD | Native GitHub integration, free for public repos | + +### Technical Notes +- Connection strings should use environment variables, not hardcoded values +- Docker volumes should be used for PostgreSQL data to persist between restarts +- Health checks should verify database connectivity + +### Gotchas +- ⚠️ Docker on Windows may have volume mounting issues - use WSL2 +- ⚠️ PostgreSQL container needs explicit volume for data persistence +- ⚠️ EF Core tools need to be installed for migrations + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Development: [How to Run Vibe](../development/how-to-run-vibe.md) +- Application Plan: [Application Plan](../architecture/application-plan.md) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/lesson-management.md b/docs/features/lesson-management.md new file mode 100644 index 0000000..1ecfe7b --- /dev/null +++ b/docs/features/lesson-management.md @@ -0,0 +1,361 @@ +# Feature: Lesson & Content Management + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: High +> **Estimate**: 10-16 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, User Authentication, Vocabulary System, Quiz System, Story Integration + +--- + +## 📌 Overview + +### Purpose +Implement the core lesson management system that allows users to browse, access, and progress through structured German lessons organized by CEFR levels (A1-C1). + +### User Story +As a learner, I want to access structured lessons organized by difficulty level so that I can systematically learn German from A1 to C1. + +### Acceptance Criteria +- [ ] Lessons are organized by CEFR levels (A1, A2, B1, B2, C1) +- [ ] Lessons within each level must be completed in order +- [ ] Each lesson contains vocabulary, grammar, story segment, and exercises +- [ ] Users can view lesson details and content +- [ ] Progress is tracked per user per lesson +- [ ] Lessons unlock sequentially based on quiz completion (80% passing score) + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | Create/Read/Update/Delete lessons (Admin) | High | +| FR-002 | List lessons by level | High | +| FR-003 | Get lesson details with all content | High | +| FR-004 | Track user progress through lessons | High | +| FR-005 | Enforce sequential lesson unlocking | High | +| FR-006 | Lesson contains: vocabulary, grammar, story, reading, listening, speaking, writing, quiz | High | +| FR-007 | Calculate level completion percentage | Medium | +| FR-008 | Admin can reorder lessons | Low | + +### Non-Functional Requirements +- Performance: Lesson listing < 200ms +- Performance: Lesson details < 500ms +- Security: Only authorized admins can create/edit lessons +- Data Integrity: Lesson order must be maintained + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: LessonsController, LessonService, LevelService +- **Database**: Levels, Lessons, UserProgress tables +- **Models**: Level, Lesson, LessonDto, LevelDto +- **Frontend**: LessonPage, LessonList, LessonCard components + +### Data Flow +``` +1. User requests lessons for A1 level +2. Backend queries Lessons table filtered by LevelId = A1 +3. Backend checks UserProgress to determine which lessons are unlocked +4. Backend returns list of lessons with locked/unlocked status +5. User selects a lesson +6. Backend returns full lesson content (vocabulary, grammar, story, etc.) +7. User completes lesson activities +8. User takes quiz +9. If quiz passed (80%), mark lesson as completed and unlock next lesson +``` + +### API Endpoints +| Endpoint | Method | Description | Auth Required | +|----------|--------|-------------|----------------| +| `/api/levels` | GET | List all CEFR levels | Yes | +| `/api/levels/{levelId}/lessons` | GET | List lessons for a specific level | Yes | +| `/api/lessons/{id}` | GET | Get full lesson details with all content | Yes | +| `/api/lessons` | POST | Create new lesson (Admin) | Yes | +| `/api/lessons/{id}` | PUT | Update lesson (Admin) | Yes | +| `/api/lessons/{id}` | DELETE | Delete lesson (Admin) | Yes | +| `/api/lessons/{id}/reorder` | POST | Reorder lessons (Admin) | Yes | + +### Database Schema (from application-plan.md) +```sql +-- Levels table +CREATE TABLE Levels ( + Id SERIAL PRIMARY KEY, + Name VARCHAR(10) UNIQUE NOT NULL, -- e.g., "A1", "A2" + Description TEXT, + Order INT UNIQUE NOT NULL +); + +-- Lessons table +CREATE TABLE Lessons ( + Id SERIAL PRIMARY KEY, + LevelId INT REFERENCES Levels(Id) ON DELETE CASCADE, + Title VARCHAR(100) NOT NULL, + Order INT NOT NULL, + Topic VARCHAR(100) NOT NULL, + UNIQUE(LevelId, Order) +); + +-- User Progress table +CREATE TABLE UserProgress ( + Id SERIAL PRIMARY KEY, + UserId INT REFERENCES Users(Id) ON DELETE CASCADE, + LessonId INT, + IsCompleted BOOLEAN DEFAULT FALSE, + QuizScore INT, + LastAttemptDate TIMESTAMP DEFAULT NOW(), + UNIQUE(UserId, LessonId) +); +``` + +### Lesson Content Structure +Each lesson contains: +1. **Vocabulary**: 5-10 words with translations, articles, audio +2. **Grammar**: Concept explanation with examples and audio +3. **Story Segment**: Part of continuous narrative using lesson vocabulary +4. **Reading**: Short text with comprehension questions +5. **Listening**: Audio exercises with questions +6. **Speaking**: Recording exercises +7. **Writing**: Open-ended prompts +8. **Quiz**: 10-15 questions to test understanding + +--- + +## 🚀 Implementation Plan + +### Phase 1: Database & Models (2-3 hours) +- [ ] Create Level entity and repository +- [ ] Create Lesson entity and repository +- [ ] Create UserProgress entity and repository +- [ ] Set up relationships between entities +- [ ] Create migrations for Levels, Lessons, UserProgress +- [ ] Seed initial A1 level and lessons + +### Phase 2: Backend Services (3-5 hours) +- [ ] Create LevelService with CRUD operations +- [ ] Create LessonService with CRUD operations +- [ ] Create ProgressService to track user progress +- [ ] Implement sequential unlocking logic +- [ ] Create DTOs for Level, Lesson, UserProgress +- [ ] Create mapping profiles (AutoMapper or manual) + +### Phase 3: API Controllers (2-3 hours) +- [ ] Create LevelsController +- [ ] Create LessonsController +- [ ] Add authorization (Admin for write operations) +- [ ] Add validation for lesson data +- [ ] Implement proper error handling + +### Phase 4: Business Logic (2-3 hours) +- [ ] Implement lesson unlocking logic +- [ ] Calculate level completion percentage +- [ ] Add next lesson recommendation +- [ ] Implement lesson order validation + +### Phase 5: Integration with Other Features (1-2 hours) +- [ ] Integrate with Vocabulary system +- [ ] Integrate with Story system +- [ ] Integrate with Quiz system +- [ ] Update user progress on quiz completion + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Database & Models | - | ⏳ | +| Backend Services | - | ⏳ | +| API Controllers | - | ⏳ | +| Business Logic | - | ⏳ | +| Integration | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Create Domain/Entities/Level.cs +- [ ] Create Domain/Entities/Lesson.cs +- [ ] Create Domain/Entities/UserProgress.cs +- [ ] Create Domain/Interfaces/ILevelRepository.cs +- [ ] Create Domain/Interfaces/ILessonRepository.cs +- [ ] Create Infrastructure/Data/Repositories/LevelRepository.cs +- [ ] Create Infrastructure/Data/Repositories/LessonRepository.cs +- [ ] Create Infrastructure/Data/Repositories/UserProgressRepository.cs +- [ ] Create Application/DTOs/LevelDto.cs +- [ ] Create Application/DTOs/LessonDto.cs +- [ ] Create Application/DTOs/UserProgressDto.cs +- [ ] Create Application/Services/LevelService.cs +- [ ] Create Application/Services/LessonService.cs +- [ ] Create Application/Services/ProgressService.cs +- [ ] Create Presentation/Controllers/LevelsController.cs +- [ ] Create Presentation/Controllers/LessonsController.cs +- [ ] Register services in Program.cs +- [ ] Write unit tests for services +- [ ] Write integration tests for controllers + +### Database +- [ ] Create migration for Levels table +- [ ] Create migration for Lessons table +- [ ] Create migration for UserProgress table +- [ ] Seed A1 level with initial lessons +- [ ] Add indexes for performance + +### Business Logic +- [ ] Implement LessonUnlockService +- [ ] Implement LevelCompletionCalculator +- [ ] Add validation for lesson order +- [ ] Add authorization checks + +### Integration +- [ ] Integrate with Vocabulary feature +- [ ] Integrate with Story feature +- [ ] Integrate with Quiz feature +- [ ] Update progress when quiz is passed + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required (backend project and database) +- [User Authentication](user-authentication.md) - Required (for user-specific progress tracking) +- Vocabulary System - Required (lesson content) +- Story Integration - Required (lesson content) +- Quiz System - Required (lesson completion) + +### Technical Dependencies +- Entity Framework Core +- AutoMapper (optional, for DTO mapping) + +### Blockers +- [ ] Infrastructure Setup must be complete +- [ ] User Authentication must be complete for progress tracking + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Lesson Management-Specific Criteria +- [ ] All CEFR levels (A1-C1) can be created and managed +- [ ] Lessons can be created, read, updated, and deleted +- [ ] Lessons are properly ordered within levels +- [ ] Sequential lesson unlocking works correctly +- [ ] User progress is tracked per lesson +- [ ] Level completion percentage is calculated correctly +- [ ] Next lesson recommendation works +- [ ] Admin can reorder lessons +- [ ] All lesson content (vocabulary, grammar, story, etc.) can be associated + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Lesson Management-Specific Tests + +#### Backend Tests +- [ ] Create level → success +- [ ] Create level with duplicate name → error +- [ ] Get all levels → returns correct list +- [ ] Create lesson → success +- [ ] Create lesson with duplicate (level, order) → error +- [ ] Get lessons by level → returns correct list +- [ ] Get lesson by ID → returns correct lesson +- [ ] Update lesson → success +- [ ] Update lesson order → updates correctly, cascades to other lessons +- [ ] Delete lesson → success, updates other lessons' order +- [ ] Get user progress for lesson → returns correct status +- [ ] Complete lesson → updates progress, unlocks next lesson +- [ ] Calculate level completion → returns correct percentage + +#### Business Logic Tests +- [ ] Lesson unlocking with completed previous lesson → success +- [ ] Lesson unlocking without completed previous lesson → fails +- [ ] Level completion with all lessons completed → 100% +- [ ] Level completion with some lessons completed → correct % +- [ ] Lesson reordering updates all subsequent lessons → correct +- [ ] Lesson deletion updates all subsequent lessons → correct + +#### Integration Tests +- [ ] Create lesson with vocabulary → both created +- [ ] Create lesson with quiz → both created +- [ ] Complete lesson → triggers quiz availability +- [ ] User progress updates when quiz passed → lesson marked complete + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Sequential lesson unlocking | Ensures users learn foundation before advanced topics | +| May 31, 2025 | 80% passing score | Balance between rigor and user frustration | +| May 31, 2025 | Lessons organized by CEFR levels | Industry standard for language learning | +| May 31, 2025 | Unlimited quiz retakes | Encourages practice without penalty | + +### Technical Notes +- Lesson order is critical - use database UNIQUE constraint on (LevelId, Order) +- UserProgress table tracks both completion status and quiz scores +- Progress calculation: (completed lessons / total lessons in level) * 100 +- Lesson unlocking: User can access lesson N only if lesson N-1 is completed + +### Gotchas +- ⚠️ Lesson reordering must update all subsequent lessons' Order values +- ⚠️ Deleting a lesson requires updating all lessons with higher Order values +- ⚠️ UserProgress.LessonId can be NULL for lessons not yet started +- ⚠️ Need to handle case where user hasn't started any lessons in a level + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Feature: [Vocabulary System](vocabulary-system.md) +- Feature: [Story Integration](story-integration.md) +- Feature: [Quiz System](quiz-system.md) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/quiz-system.md b/docs/features/quiz-system.md new file mode 100644 index 0000000..f7a7caa --- /dev/null +++ b/docs/features/quiz-system.md @@ -0,0 +1,460 @@ +# Feature: Quiz System + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: High +> **Estimate**: 6-10 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, Lesson Management, User Authentication, AI Services (TTS) + +--- + +## 📌 Overview + +### Purpose +Implement a comprehensive quiz system that tests users' understanding of lesson material through various question types, with automatic scoring and progress tracking. + +### User Story +As a learner, I want to take quizzes after each lesson to test my understanding and receive immediate feedback on my performance. + +### Acceptance Criteria +- [ ] Each lesson has an associated quiz +- [ ] Quiz contains 10-15 questions of various types +- [ ] Question types: Multiple Choice, Fill-in-the-Blank, Listening Comprehension, Matching +- [ ] Static difficulty (same questions for all users) +- [ ] 80% passing score required to complete lesson +- [ ] Unlimited retakes allowed +- [ ] Score is recorded in UserProgress table +- [ ] Quiz results show which questions were correct/incorrect + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | Create/Read/Update/Delete quizzes (Admin) | High | +| FR-002 | Associate quizzes with lessons | High | +| FR-003 | Support multiple question types | High | +| FR-004 | Support MCQ questions | High | +| FR-005 | Support fill-in-the-blank questions | High | +| FR-006 | Support listening comprehension questions | High | +| FR-007 | Support matching questions | High | +| FR-008 | Calculate quiz score and passing status | High | +| FR-009 | Record quiz attempts in UserProgress | High | +| FR-010 | Return detailed results with correct/incorrect answers | Medium | +| FR-011 | Allow unlimited retakes | Medium | +| FR-012 | Configurable passing score (default 80%) | Medium | + +### Non-Functional Requirements +- Performance: Quiz loading < 300ms +- Performance: Quiz submission < 500ms +- Security: Only authorized admins can create/edit quizzes +- Data Integrity: Quiz-question relationships must be maintained + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: QuizzesController, QuizService, QuestionService +- **Database**: Quizzes, Questions tables +- **Models**: Quiz, Question, QuizDto, QuestionDto, AnswerDto, QuizResult +- **External**: Coqui TTS (for listening questions audio) +- **Frontend**: QuizPage, QuizTab, McqQuestion, FillInBlank, ListeningQuestion, MatchingQuestion components + +### Data Flow +``` +1. User completes lesson activities +2. User navigates to quiz for the lesson +3. Backend retrieves quiz and questions for the lesson +4. Frontend displays quiz to user +5. User answers all questions +6. Frontend sends answers to backend +7. Backend scores quiz and determines pass/fail +8. Backend updates UserProgress with score and completion status +9. Backend returns detailed results to frontend +10. Frontend displays results with feedback +``` + +### API Endpoints +| Endpoint | Method | Description | Auth Required | +|----------|--------|-------------|----------------| +| `/api/quizzes` | GET | List all quizzes | Yes | +| `/api/quizzes/{id}` | GET | Get quiz with questions | Yes | +| `/api/lessons/{lessonId}/quiz` | GET | Get quiz for a specific lesson | Yes | +| `/api/quizzes` | POST | Create new quiz (Admin) | Yes | +| `/api/quizzes/{id}` | PUT | Update quiz (Admin) | Yes | +| `/api/quizzes/{id}` | DELETE | Delete quiz (Admin) | Yes | +| `/api/quizzes/{quizId}/questions` | GET | List questions for a quiz | Yes | +| `/api/questions` | POST | Add question to quiz (Admin) | Yes | +| `/api/questions/{id}` | PUT | Update question (Admin) | Yes | +| `/api/questions/{id}` | DELETE | Delete question (Admin) | Yes | +| `/api/quizzes/{quizId}/submit` | POST | Submit quiz answers | Yes | +| `/api/quizzes/{quizId}/results` | GET | Get quiz results history | Yes | + +### Database Schema (from application-plan.md) +```sql +-- Quizzes table +CREATE TABLE Quizzes ( + Id SERIAL PRIMARY KEY, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + PassingScore INT DEFAULT 80 +); + +-- Questions table +CREATE TABLE Questions ( + Id SERIAL PRIMARY KEY, + QuizId INT REFERENCES Quizzes(Id) ON DELETE CASCADE, + Type VARCHAR(20) NOT NULL CHECK (Type IN ('mcq', 'fill_in', 'listening', 'matching')), + Content TEXT NOT NULL, + CorrectAnswer TEXT NOT NULL, + AudioUrl VARCHAR(255), + Options TEXT[], -- For MCQ: ['option1', 'option2'] + Points INT DEFAULT 1 +); +``` + +### Question Types + +#### 1. Multiple Choice (mcq) +```csharp +{ + Type: "mcq", + Content: "What is the article for 'Buch'?", + CorrectAnswer: "das", + Options: ["der", "die", "das"], + Points: 1 +} +``` + +#### 2. Fill-in-the-Blank (fill_in) +```csharp +{ + Type: "fill_in", + Content: "Anna _____ um 7 Uhr auf.", + CorrectAnswer: "steht", + Options: null, + Points: 1 +} +``` + +#### 3. Listening Comprehension (listening) +```csharp +{ + Type: "listening", + Content: "What did you hear?", + CorrectAnswer: "This is a book.", + AudioUrl: "/audio/quiz/1.wav", + Options: ["This is a book.", "This is a cat."], + Points: 2 // More points for harder questions +} +``` + +#### 4. Matching (matching) +```csharp +{ + Type: "matching", + Content: "Match the word to its article", + CorrectAnswer: "der", // For "Buch" in this case + Options: ["Buch", "Frau", "Kind"], // Words to match + Points: 1 +} +// Note: Matching questions may need special handling +// Alternative: Store as JSON with pairs +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Database & Models (2-3 hours) +- [ ] Create Quiz entity +- [ ] Create Question entity +- [ ] Create QuizDto, QuestionDto, AnswerDto +- [ ] Create QuizResult model +- [ ] Create IQuizRepository interface +- [ ] Create IQuestionRepository interface +- [ ] Create repository implementations +- [ ] Create migrations for Quizzes and Questions tables + +### Phase 2: Backend Services (2-3 hours) +- [ ] Create QuizService with CRUD operations +- [ ] Create QuestionService with CRUD operations +- [ ] Create QuizSubmissionService for handling answers +- [ ] Implement scoring logic +- [ ] Implement pass/fail determination +- [ ] Update UserProgress on quiz completion +- [ ] Create mapping between entities and DTOs + +### Phase 3: API Controllers (2 hours) +- [ ] Create QuizzesController +- [ ] Create QuestionsController +- [ ] Implement quiz submission endpoint +- [ ] Implement results endpoint +- [ ] Add authorization (Admin for write operations) +- [ ] Add validation for quiz data + +### Phase 4: Question Type Implementation (2 hours) +- [ ] Implement MCQ question handling +- [ ] Implement fill-in-the-blank question handling +- [ ] Implement listening comprehension with audio +- [ ] Implement matching question handling +- [ ] Add audio generation for listening questions + +### Phase 5: Frontend Integration (2 hours) +- [ ] Create QuizPage component +- [ ] Create QuizTab component +- [ ] Create McqQuestion component +- [ ] Create FillInBlank component +- [ ] Create ListeningQuestion component +- [ ] Create MatchingQuestion component +- [ ] Create QuizResults component +- [ ] Add quiz to LessonPage + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Database & Models | - | ⏳ | +| Backend Services | - | ⏳ | +| API Controllers | - | ⏳ | +| Question Type Implementation | - | ⏳ | +| Frontend Integration | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Create Domain/Entities/Quiz.cs +- [ ] Create Domain/Entities/Question.cs +- [ ] Create Application/DTOs/QuizDto.cs +- [ ] Create Application/DTOs/QuestionDto.cs +- [ ] Create Application/DTOs/AnswerDto.cs +- [ ] Create Application/DTOs/QuizResult.cs +- [ ] Create Domain/Interfaces/IQuizRepository.cs +- [ ] Create Domain/Interfaces/IQuestionRepository.cs +- [ ] Create Infrastructure/Data/Repositories/QuizRepository.cs +- [ ] Create Infrastructure/Data/Repositories/QuestionRepository.cs +- [ ] Create Application/Services/QuizService.cs +- [ ] Create Application/Services/QuestionService.cs +- [ ] Create Application/Services/QuizSubmissionService.cs +- [ ] Create Presentation/Controllers/QuizzesController.cs +- [ ] Create Presentation/Controllers/QuestionsController.cs +- [ ] Integrate with UserProgress from Lesson Management +- [ ] Register services in Program.cs +- [ ] Write unit tests for services +- [ ] Write integration tests for controllers + +### Database +- [ ] Create migration for Quizzes table +- [ ] Create migration for Questions table +- [ ] Add foreign key from Quizzes to Lessons +- [ ] Add indexes for QuizId, Type +- [ ] Apply migrations + +### Audio Generation +- [ ] Extend Coqui TTS service for quiz questions +- [ ] Generate audio for listening comprehension questions +- [ ] Store audio files with quiz question IDs + +### Question Types +- [ ] Implement MCQ answer validation +- [ ] Implement fill-in-the-blank answer validation +- [ ] Implement listening comprehension answer validation +- [ ] Implement matching answer validation +- [ ] Add case-insensitive comparison where appropriate +- [ ] Add whitespace trimming for answers + +### Frontend +- [ ] Create pages/QuizPage.tsx +- [ ] Create components/QuizTab.tsx +- [ ] Create components/questions/McqQuestion.tsx +- [ ] Create components/questions/FillInBlank.tsx +- [ ] Create components/questions/ListeningQuestion.tsx +- [ ] Create components/questions/MatchingQuestion.tsx +- [ ] Create components/QuizResults.tsx +- [ ] Create hooks/useQuiz.ts +- [ ] Integrate AudioPlayer for listening questions +- [ ] Add quiz timer (optional) +- [ ] Add progress indicator + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required +- [Lesson Management](lesson-management.md) - Required (quizzes associated with lessons) +- [User Authentication](user-authentication.md) - Required (for tracking progress) +- [AI Services - TTS](ai-services.md) - Required (for listening question audio) + +### Technical Dependencies +- Entity Framework Core +- AutoMapper (optional) + +### Blockers +- [ ] Infrastructure Setup must be complete +- [ ] Lesson Management must be complete +- [ ] User Authentication must be complete for progress tracking + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Quiz-Specific Criteria +- [ ] Quizzes can be created, read, updated, and deleted +- [ ] Questions can be added to, updated in, and removed from quizzes +- [ ] All question types work correctly (MCQ, Fill-in, Listening, Matching) +- [ ] Quiz submission works and returns score +- [ ] Pass/fail determination is correct (80% threshold) +- [ ] User progress is updated on quiz completion +- [ ] Quiz results show correct/incorrect answers +- [ ] Unlimited retakes are allowed +- [ ] Audio for listening questions is generated and accessible + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Quiz-Specific Tests + +#### Backend Tests +- [ ] Create quiz → success +- [ ] Create quiz with invalid lesson → error +- [ ] Get quiz by ID → returns correct quiz with questions +- [ ] Get quiz by lesson → returns correct quiz +- [ ] Add question to quiz → success +- [ ] Add MCQ question → success +- [ ] Add fill-in question → success +- [ ] Add listening question → success +- [ ] Add matching question → success +- [ ] Update question → success +- [ ] Delete question → success +- [ ] Submit quiz with all correct answers → 100% score +- [ ] Submit quiz with some correct answers → partial score +- [ ] Submit quiz with all wrong answers → 0% score +- [ ] Submit quiz with passing score → user progress updated +- [ ] Submit quiz with failing score → user progress not updated +- [ ] Delete quiz → cascades to questions + +#### Question Type Tests +- [ ] MCQ: Correct answer → scored correctly +- [ ] MCQ: Incorrect answer → scored incorrectly +- [ ] Fill-in: Exact match → scored correctly +- [ ] Fill-in: Case-insensitive match → scored correctly +- [ ] Fill-in: Whitespace variation → scored correctly +- [ ] Fill-in: Close but wrong → scored incorrectly +- [ ] Listening: Correct transcription → scored correctly +- [ ] Listening: Incorrect transcription → scored incorrectly +- [ ] Matching: All pairs correct → scored correctly +- [ ] Matching: Some pairs correct → partial score + +#### Business Logic Tests +- [ ] Scoring with default points → correct calculation +- [ ] Scoring with custom points → correct calculation +- [ ] Pass/fail at 80% threshold → correct determination +- [ ] Pass/fail at exactly 80% → passes +- [ ] Pass/fail at 79% → fails +- [ ] User progress updates on first pass → lesson marked complete +- [ ] User progress updates on subsequent pass → no change (already complete) + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Static difficulty | Simpler implementation, consistent experience for all users | +| May 31, 2025 | 80% passing score | Industry standard, rigorous but achievable | +| May 31, 2025 | Unlimited retakes | Encourages practice, reduces anxiety | +| May 31, 2025 | Multiple question types | Varied testing, caters to different learning styles | +| May 31, 2025 | Store AudioUrl on questions | Enables listening comprehension questions | + +### Technical Notes +- Questions table uses PostgreSQL TEXT[] array type for Options +- Each question has a Points value for weighted scoring +- Quiz.PassingScore is the percentage required (default 80) +- QuizResult should include: Passed, Score, TotalPoints, QuestionResults[] +- QuestionResults should include: QuestionId, UserAnswer, CorrectAnswer, IsCorrect, PointsEarned + +### Question Type Validation Logic + +#### MCQ +- UserAnswer must exactly match one of the Options +- Case-insensitive comparison recommended + +#### Fill-in-the-Blank +- UserAnswer compared to CorrectAnswer +- Trim whitespace from both before comparison +- Case-insensitive comparison recommended +- Consider allowing minor variations (e.g., "steht" vs "stehen") + +#### Listening Comprehension +- UserAnswer must exactly match CorrectAnswer (or one of Options for MCQ-style listening) +- AudioUrl must be present and valid + +#### Matching +- Need to handle multiple pairs +- Consider storing as: `{ pairs: [{left: "Buch", right: "das"}, ...] }` +- Validate that all pairs are correct + +### Gotchas +- ⚠️ Listening questions require audio generation - can be slow +- ⚠️ Fill-in-the-blank needs careful validation (accents, case, etc.) +- ⚠️ Matching questions need special data structure +- ⚠️ Quiz deletion must cascade to Questions (ON DELETE CASCADE) +- ⚠️ Question updates must maintain quiz integrity + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Feature: [Lesson Management](lesson-management.md) +- Feature: [User Authentication](user-authentication.md) +- Feature: [AI Services](ai-services.md) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/story-integration.md b/docs/features/story-integration.md new file mode 100644 index 0000000..02c5770 --- /dev/null +++ b/docs/features/story-integration.md @@ -0,0 +1,411 @@ +# Feature: Story Integration + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: High +> **Estimate**: 8-12 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, Lesson Management, AI Services (Mistral, TTS) + +--- + +## 📌 Overview + +### Purpose +Implement a continuous story narrative that unlocks segment by segment as users complete lessons, with AI-generated content tailored to each level and lesson's vocabulary. + +### User Story +As a learner, I want to follow a continuous story that uses the vocabulary and grammar I'm learning, so that I can see the language in context and stay motivated through narrative progression. + +### Acceptance Criteria +- [ ] Each level has a continuous story (e.g., A1: "A Week in the Life of Anna") +- [ ] Story is divided into segments, one per lesson +- [ ] Story segments unlock after completing the associated lesson's quiz +- [ ] Story content uses only vocabulary and grammar from completed lessons +- [ ] Each story segment has associated audio (Coqui TTS) +- [ ] Users can click on words to see translations +- [ ] Story progress is tracked separately from lesson progress + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | Create/Read/Update/Delete story segments (Admin) | High | +| FR-002 | Associate story segments with levels and lessons | High | +| FR-003 | Generate story content using Mistral-Medium AI | High | +| FR-004 | Generate audio for story segments using Coqui TTS | High | +| FR-005 | Sequential story unlocking based on lesson completion | High | +| FR-006 | Display story with clickable words for translations | High | +| FR-007 | Track story progress per user | Medium | +| FR-008 | Support multiple stories per level (optional) | Low | + +### Non-Functional Requirements +- Performance: Story segment loading < 300ms +- Storage: Audio files ~100-500KB per segment +- Security: Only admins can create/edit story segments +- Data Integrity: Story-level-lesson associations must be valid + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: StoryController, StoryService, StoryGenerationService +- **Database**: StorySegments table +- **Models**: StorySegment, StorySegmentDto, StoryGenerationRequest +- **External**: Mistral-Medium API, Coqui TTS +- **Frontend**: StoryPage, StoryTab, StorySegment, StoryPlayer components + +### Data Flow +``` +1. Admin triggers story generation for a level +2. Backend calls Mistral-Medium with vocabulary from all lessons in level +3. Mistral returns story text divided into segments +4. For each segment: + a. Store in StorySegments table + b. Associate with level and lesson + c. Generate audio using Coqui TTS + d. Save audio file and update AudioUrl +5. User completes lesson quiz (80%+) +6. Backend unlocks next story segment for user +7. User reads/listens to story segment +8. User can click on words to see translations +``` + +### API Endpoints +| Endpoint | Method | Description | Auth Required | +|----------|--------|-------------|----------------| +| `/api/story/levels` | GET | List levels with stories | Yes | +| `/api/story/{levelId}/segments` | GET | List story segments for a level | Yes | +| `/api/story/segments/{id}` | GET | Get specific story segment | Yes | +| `/api/story/segments` | POST | Create/update story segment (Admin) | Yes | +| `/api/story/segments/{id}` | DELETE | Delete story segment (Admin) | Yes | +| `/api/story/generate` | POST | Generate story for level (Admin) | Yes | +| `/api/story/{levelId}/progress` | GET | Get user's story progress for level | Yes | +| `/api/story/segments/{id}/audio` | GET | Get audio file for segment | Yes | + +### Database Schema (from application-plan.md) +```sql +CREATE TABLE StorySegments ( + Id SERIAL PRIMARY KEY, + LevelId INT REFERENCES Levels(Id) ON DELETE CASCADE, + LessonId INT REFERENCES Lessons(Id) ON DELETE SET NULL, + Content TEXT NOT NULL, + AudioUrl VARCHAR(255), + Order INT NOT NULL, + UNIQUE(LevelId, Order) +); +``` + +### Story Generation Prompt +``` +Write a 2–3 paragraph story about daily life for a German {level} learner. +Use only {level} vocabulary and grammar (present tense, basic nouns/verbs). +Include the following words: {vocabularyList}. +Theme: "{theme}". +Divide the story into {segmentCount} segments, one for each lesson. +``` + +### Example Story Segment (from application-plan.md) +``` +Id: 1 +LevelId: 1 (A1) +LessonId: 1 +Content: "Anna steht jeden Morgen um 7 Uhr auf. Sie geht ins Badezimmer und wäscht sich das Gesicht. Dann frühstückt sie in der Küche. Sie isst ein Brot und trinkt einen Kaffee." +AudioUrl: "/audio/story/1.wav" +Order: 1 +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Database & Models (2 hours) +- [ ] Create StorySegment entity +- [ ] Create StorySegmentDto +- [ ] Create StoryGenerationRequest DTO +- [ ] Create IStoryRepository interface +- [ ] Create StoryRepository implementation +- [ ] Create migration for StorySegments table +- [ ] Add relationships to Level and Lesson entities + +### Phase 2: Backend Services (2-3 hours) +- [ ] Create StoryService with CRUD operations +- [ ] Create StoryGenerationService for AI integration +- [ ] Implement Mistral-Medium API client +- [ ] Implement segment generation logic +- [ ] Create story segment ordering logic +- [ ] Write unit tests for services + +### Phase 3: AI Integration (2-3 hours) +- [ ] Configure Mistral-Medium API client +- [ ] Create prompt templates for each level +- [ ] Implement vocabulary extraction from lessons +- [ ] Implement story text segmentation +- [ ] Handle AI API errors gracefully +- [ ] Add retry logic for failed generations + +### Phase 4: Audio Generation (2 hours) +- [ ] Integrate with Coqui TTS service +- [ ] Generate audio for each story segment +- [ ] Store audio files with consistent naming +- [ ] Update StorySegment.AudioUrl after generation +- [ ] Add audio file serving endpoint + +### Phase 5: Frontend Integration (2-3 hours) +- [ ] Create StoryPage component +- [ ] Create StoryTab component +- [ ] Create StorySegment component +- [ ] Create StoryPlayer component with audio +- [ ] Implement word click-to-translate functionality +- [ ] Add story progress tracking +- [ ] Integrate with LessonPage + +### Phase 6: User Progress (1-2 hours) +- [ ] Track which story segments user has unlocked +- [ ] Update unlock status when lesson is completed +- [ ] Display locked segments as "coming soon" +- [ ] Show progress through story + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Database & Models | - | ⏳ | +| Backend Services | - | ⏳ | +| AI Integration | - | ⏳ | +| Audio Generation | - | ⏳ | +| Frontend Integration | - | ⏳ | +| User Progress | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Create Domain/Entities/StorySegment.cs +- [ ] Create Application/DTOs/StorySegmentDto.cs +- [ ] Create Application/DTOs/StoryGenerationRequest.cs +- [ ] Create Domain/Interfaces/IStoryRepository.cs +- [ ] Create Infrastructure/Data/Repositories/StoryRepository.cs +- [ ] Create Application/Services/StoryService.cs +- [ ] Create Application/Services/StoryGenerationService.cs +- [ ] Create Application/Services/MistralClientService.cs +- [ ] Create Presentation/Controllers/StoryController.cs +- [ ] Update Level and Lesson entities with StorySegment relationships +- [ ] Register services in Program.cs +- [ ] Write unit tests +- [ ] Write integration tests + +### Database +- [ ] Create migration for StorySegments table +- [ ] Add foreign keys to Levels and Lessons +- [ ] Add indexes for LevelId, LessonId, Order +- [ ] Apply migration + +### AI Integration +- [ ] Set up Mistral API client with configuration +- [ ] Create prompt templates for each CEFR level +- [ ] Implement vocabulary extraction from lessons +- [ ] Implement story segmentation logic +- [ ] Add error handling for Mistral API +- [ ] Add rate limiting/caching for Mistral calls + +### Audio Generation +- [ ] Extend Coqui TTS service for story segments +- [ ] Implement batch audio generation +- [ ] Create audio file storage structure +- [ ] Add audio serving endpoint +- [ ] Implement audio cleanup mechanism + +### Frontend +- [ ] Create pages/StoryPage.tsx +- [ ] Create components/StoryTab.tsx +- [ ] Create components/StorySegment.tsx +- [ ] Create components/StoryPlayer.tsx +- [ ] Create hooks/useStory.ts +- [ ] Create hooks/useStoryAudio.ts +- [ ] Implement word translation on click +- [ ] Add story progress indicator +- [ ] Add navigation between segments + +### User Progress +- [ ] Extend UserProgress or create StoryProgress table +- [ ] Implement unlock logic when lesson completed +- [ ] Create endpoint to get user's story progress +- [ ] Add locked segment UI state + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required +- [Lesson Management](lesson-management.md) - Required (story segments associated with lessons/levels) +- [AI Services](ai-services.md) - Required (Mistral for generation, Coqui for audio) + +### Technical Dependencies +- Mistral-Medium API access +- Coqui TTS Python library +- Entity Framework Core + +### Blockers +- [ ] Infrastructure Setup must be complete +- [ ] Lesson Management must be complete +- [ ] AI Services must be configured +- [ ] Mistral API key required + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Story-Specific Criteria +- [ ] Story segments can be created, read, updated, and deleted +- [ ] Stories are associated with levels and lessons +- [ ] Story content is generated using Mistral-Medium +- [ ] Story audio is generated using Coqui TTS +- [ ] Story segments unlock sequentially with lesson completion +- [ ] Users can view unlocked story segments +- [ ] Users can listen to story audio +- [ ] Users can click on words to see translations +- [ ] Story progress is tracked per user + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Story-Specific Tests + +#### Backend Tests +- [ ] Create story segment → success +- [ ] Create story segment with invalid level → error +- [ ] Get story segments by level → returns correct list +- [ ] Get story segment by ID → returns correct segment +- [ ] Update story segment → success +- [ ] Delete story segment → success +- [ ] Generate story with Mistral → returns valid story text +- [ ] Generate story with vocabulary list → includes all words +- [ ] Generate audio for story segment → creates audio file +- [ ] Get user's story progress → returns correct status + +#### AI Tests +- [ ] Mistral generates valid German story for A1 level +- [ ] Mistral generates story with all requested vocabulary +- [ ] Mistral handles different CEFR levels appropriately +- [ ] Coqui TTS generates clear audio for story text +- [ ] Coqui TTS handles long story segments +- [ ] Coqui TTS handles German special characters (umlauts, ß) + +#### Frontend Tests +- [ ] Story page loads and displays segments +- [ ] Audio plays for story segment +- [ ] Word click shows translation +- [ ] Navigation between segments works +- [ ] Locked segments are displayed as "coming soon" +- [ ] Story progress is displayed correctly + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | One story per level for MVP | Simpler implementation, can expand later | +| May 31, 2025 | Segment per lesson | Natural progression, matches lesson structure | +| May 31, 2025 | Generate audio for all segments | Essential for accessibility and pronunciation | +| May 31, 2025 | Use Mistral-Medium | High-quality, cost-effective for this use case | +| May 31, 2025 | Click-to-translate words | Enhances learning without disrupting flow | + +### Technical Notes +- StorySegments.LessonId can be NULL for intro/conclusion segments +- Order field ensures segments appear in correct sequence +- UNIQUE(LevelId, Order) prevents duplicate ordering +- Content should be stored as plain text, formatted on frontend +- Audio files should be named: `/audio/story/{levelId}-{order}.wav` + +### Prompt Engineering +Different prompts for different levels: +- **A1**: Simple present tense, basic vocabulary, short sentences +- **A2**: Present, past, future tense, expanded vocabulary +- **B1**: Complex sentences, subordinate clauses, broader topics +- **B2/C1**: Advanced grammar, idiomatic expressions, nuanced topics + +### Gotchas +- ⚠️ Mistral API has rate limits - implement caching +- ⚠️ AI generation may produce non-A1 vocabulary - need validation +- ⚠️ Long stories may exceed token limits - need segmentation +- ⚠️ Audio generation for long segments may be slow +- ⚠️ Need to handle cases where vocabulary list is empty + +### Example Prompts by Level + +**A1 Prompt:** +``` +Write a 2-3 paragraph story about daily life in Germany for an A1 learner. +Use only A1 vocabulary and simple present tense sentences. +Include these words: {vocabularyList} +Theme: {theme} +``` + +**B1 Prompt:** +``` +Write a 3-4 paragraph story about {theme} for a B1 German learner. +Use B1-level vocabulary and grammar including present, past, and future tenses. +Include these words: {vocabularyList} +Make the story engaging and suitable for adult learners. +``` + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Feature: [Lesson Management](lesson-management.md) +- Feature: [AI Services](ai-services.md) +- Reference: [Mistral AI API](https://docs.mistral.ai/) +- Reference: [Coqui TTS](https://github.com/coqui-ai/TTS) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/template.md b/docs/features/template.md new file mode 100644 index 0000000..b9defe1 --- /dev/null +++ b/docs/features/template.md @@ -0,0 +1,187 @@ +# Feature: [Feature Name] + +> **Status**: ⏳ Planned +> **Priority**: Medium +> **Complexity**: Medium +> **Estimate**: 3 days +> **Assignee**: [Your Name] +> **Created**: [YYYY-MM-DD] +> **Target Completion**: [YYYY-MM-DD] +> **PR**: [Link to Pull Request] +> **Related Features**: [List of dependent/related features] + +--- + +## 📌 Overview + +### Purpose +[Brief description of what this feature does and why it's needed] + +### User Story +[As a [user type], I want to [action] so that [benefit]] + +### Acceptance Criteria +- [ ] Criterion 1 +- [ ] Criterion 2 +- [ ] Criterion 3 + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | [Requirement description] | High | +| FR-002 | [Requirement description] | Medium | + +### Non-Functional Requirements +- Performance: [e.g., API response < 200ms] +- Security: [e.g., Role-based access control] +- Compatibility: [e.g., Works on mobile and desktop] + +--- + +## 🏗️ Technical Design + +### Architecture Diagram +``` +``` + +### Components Involved +- **Backend**: [List controllers, services, repositories] +- **Frontend**: [List components, hooks, stores] +- **Database**: [List tables, changes needed] +- **External Services**: [List any third-party services] + +### Data Flow +1. User action → Frontend → API call +2. API endpoint → Application service → Domain logic +3. Repository → Database +4. Return response → Frontend update + +### API Changes +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/new-endpoint` | POST | [Description] | + +### Database Changes +```sql +-- SQL schema changes if applicable +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Setup & Foundation +- [ ] Task 1 +- [ ] Task 2 + +### Phase 2: Core Implementation +- [ ] Task 3 +- [ ] Task 4 + +### Phase 3: Testing & Polish +- [ ] Write unit tests +- [ ] Write integration tests +- [ ] Manual testing + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Design Complete | [Date] | ⏳ | +| Backend Complete | [Date] | ⏳ | +| Frontend Complete | [Date] | ⏳ | +| Testing Complete | [Date] | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Create domain entity/models +- [ ] Create repository interface +- [ ] Implement repository +- [ ] Create application service +- [ ] Create API endpoints +- [ ] Add validation +- [ ] Write unit tests +- [ ] Write integration tests + +### Frontend +- [ ] Create components +- [ ] Add state management +- [ ] Create hooks +- [ ] Add API client calls +- [ ] Implement UI/UX +- [ ] Write component tests + +### Database +- [ ] Update schema +- [ ] Create migrations +- [ ] Seed data (if needed) + +### DevOps +- [ ] Update CI/CD pipeline +- [ ] Update deployment scripts + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Feature A](feature-a.md) - Required for authentication +- [Feature B](feature-b.md) - Provides shared components + +### Technical Dependencies +- Library/Package: Version - Purpose +- External API: Documentation link + +### Blockers +- [ ] Blocker 1 - Description +- [ ] Blocker 2 - Description + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| [Date] | [Decision] | [Why this approach] | + +### Technical Notes +- Note 1: [Important implementation detail] +- Note 2: [Workaround for limitation] + +### Gotchas +- ⚠️ [Potential pitfall and how to avoid it] + +### Lessons Learned +- [What you learned during implementation] + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| [Date] | Created | Initial plan | +| [Date] | In Progress | Started development | +| [Date] | Code Review | PR submitted | +| [Date] | Completed | Feature deployed | + +--- + +## 📎 Related Files & Links + +- Backend: [Link to code or describe location] +- Frontend: [Link to code or describe location] +- Tests: [Link to tests] +- PR: [Link to pull request] +- Design Mockups: [Link to Figma/design files] + +--- + +*Template last updated: May 31, 2025* diff --git a/docs/features/user-authentication.md b/docs/features/user-authentication.md new file mode 100644 index 0000000..85e1988 --- /dev/null +++ b/docs/features/user-authentication.md @@ -0,0 +1,325 @@ +# Feature: User Authentication & Authorization + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: Medium +> **Estimate**: 4-6 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, Lesson Management + +--- + +## 📌 Overview + +### Purpose +Implement user authentication and authorization system using ASP.NET Core Identity with JWT token-based authentication. + +### User Story +As a user, I want to register, login, and access my personalized learning content so that I can track my progress and continue from where I left off. + +### Acceptance Criteria +- [ ] Users can register with username, email, and password +- [ ] Users can login with email and password +- [ ] Users receive JWT token upon successful authentication +- [ ] JWT token is required for protected API endpoints +- [ ] Token expiration and refresh mechanism +- [ ] Password hashing for security +- [ ] Current user information available via /api/auth/me + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | User registration endpoint | High | +| FR-002 | User login endpoint | High | +| FR-003 | JWT token generation and validation | High | +| FR-004 | Protected routes require authentication | High | +| FR-005 | Current user endpoint | Medium | +| FR-006 | Password reset functionality | Low | +| FR-007 | Email verification (optional for MVP) | Low | + +### Non-Functional Requirements +- Security: Passwords hashed with bcrypt or similar +- Security: JWT tokens expire after 24 hours +- Security: Refresh tokens for seamless UX +- Performance: Authentication < 500ms +- Compatibility: Works with React frontend + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: AuthController, AuthService, JWT configuration +- **Database**: Users table (from initial schema) +- **Models**: User, LoginDto, RegisterDto, AuthResponse +- **Middleware**: JWT authentication middleware + +### Data Flow +``` +User Registration: +1. Frontend POST /api/auth/register with {username, email, password} +2. Backend validates input +3. Backend hashes password +4. Backend creates user in database +5. Backend generates JWT token +6. Returns {userId, token} to frontend + +User Login: +1. Frontend POST /api/auth/login with {email, password} +2. Backend validates credentials +3. Backend generates JWT token +4. Returns {userId, token} to frontend + +Protected Endpoint: +1. Frontend includes token in Authorization header +2. Backend middleware validates token +3. Backend processes request if valid +4. Returns 401 if token invalid/expired +``` + +### API Endpoints +| Endpoint | Method | Description | Auth Required | +|----------|--------|-------------|----------------| +| `/api/auth/register` | POST | Register new user | No | +| `/api/auth/login` | POST | Login existing user | No | +| `/api/auth/me` | GET | Get current user info | Yes | +| `/api/auth/logout` | POST | Invalidate token | Yes | +| `/api/auth/refresh` | POST | Refresh expired token | Yes | + +### Database Schema (from application-plan.md) +```sql +CREATE TABLE Users ( + Id SERIAL PRIMARY KEY, + Username VARCHAR(50) UNIQUE NOT NULL, + Email VARCHAR(100) UNIQUE NOT NULL, + PasswordHash VARCHAR(255) NOT NULL, + CurrentLevel VARCHAR(10) DEFAULT 'A1', + Streak INT DEFAULT 0, + TotalPoints INT DEFAULT 0, + CreatedAt TIMESTAMP DEFAULT NOW() +); +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Backend Authentication (3-4 hours) +- [ ] Create User model and DTOs (RegisterDto, LoginDto, AuthResponse) +- [ ] Configure ASP.NET Core Identity +- [ ] Create AuthService with user registration logic +- [ ] Create AuthService with user login logic +- [ ] Configure JWT token generation +- [ ] Create AuthController with endpoints +- [ ] Add JWT authentication middleware +- [ ] Configure CORS for frontend + +### Phase 2: Database Integration (1-2 hours) +- [ ] Update User entity to match schema +- [ ] Configure EF Core user repository +- [ ] Implement password hashing +- [ ] Create user seed data (admin user) +- [ ] Test database operations + +### Phase 3: Token Management (1 hour) +- [ ] Configure JWT settings in appsettings.json +- [ ] Implement token validation middleware +- [ ] Add token refresh mechanism +- [ ] Set up token expiration (24 hours) +- [ ] Configure refresh token rotation + +### Phase 4: Frontend Integration (Optional - if doing full stack) +- [ ] Create auth service in React +- [ ] Implement login/registration forms +- [ ] Store token in localStorage/cookies +- [ ] Add auth headers to API requests +- [ ] Handle token expiration + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Backend Auth Complete | - | ⏳ | +| Database Integration | - | ⏳ | +| Token Management | - | ⏳ | +| Frontend Integration | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Create Models/User.cs with properties +- [ ] Create DTOs/Auth/RegisterDto.cs +- [ ] Create DTOs/Auth/LoginDto.cs +- [ ] Create DTOs/Auth/AuthResponse.cs +- [ ] Create Services/AuthService.cs +- [ ] Create Controllers/AuthController.cs +- [ ] Configure JWT in Program.cs +- [ ] Add [Authorize] attribute to protected endpoints +- [ ] Create AuthMiddleware.cs +- [ ] Configure CORS policy +- [ ] Write unit tests for AuthService +- [ ] Write integration tests for AuthController + +### Database +- [ ] Update User entity mapping +- [ ] Create UserRepository +- [ ] Implement password hashing +- [ ] Create migration for Users table +- [ ] Seed admin user + +### Token Management +- [ ] Configure JWT settings +- [ ] Implement token generation +- [ ] Implement token validation +- [ ] Implement token refresh +- [ ] Set token expiration + +### Frontend (Optional) +- [ ] Create authService.ts +- [ ] Create LoginPage component +- [ ] Create RegisterPage component +- [ ] Create AuthContext for user state +- [ ] Implement protected route wrapper +- [ ] Add logout functionality + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required (backend project and database) + +### Technical Dependencies +- ASP.NET Core Identity +- JWT Bearer Authentication package +- BouncyCastle or similar for password hashing + +### Blockers +- [ ] Infrastructure Setup must be complete first + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Authentication-Specific Criteria +- [ ] Users can successfully register with valid credentials +- [ ] Users can successfully login with valid credentials +- [ ] Invalid credentials are rejected with appropriate error messages +- [ ] JWT tokens are generated and validated correctly +- [ ] Token expiration works as configured +- [ ] Token refresh mechanism works +- [ ] Protected endpoints reject requests without valid tokens +- [ ] Passwords are hashed and never stored in plain text +- [ ] Rate limiting on auth endpoints configured + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Authentication-Specific Tests + +#### Backend Tests +- [ ] Register with valid data → success +- [ ] Register with duplicate email → error +- [ ] Register with duplicate username → error +- [ ] Register with invalid password → error +- [ ] Login with valid credentials → success +- [ ] Login with invalid email → error +- [ ] Login with invalid password → error +- [ ] Login with valid token → success +- [ ] Login with expired token → error +- [ ] Login with invalid token → error +- [ ] Access protected endpoint without token → 401 +- [ ] Access protected endpoint with valid token → success +- [ ] Access protected endpoint with invalid token → 401 +- [ ] Token refresh → new valid token +- [ ] Rate limiting on login attempts → 429 after N attempts + +#### Frontend Tests +- [ ] Registration form validation +- [ ] Registration form submission +- [ ] Login form validation +- [ ] Login form submission +- [ ] Token storage in localStorage/cookies +- [ ] Token inclusion in API requests +- [ ] Protected route redirection +- [ ] Logout functionality +- [ ] Session expiration handling + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Use JWT over cookies | Stateless, works well with SPAs, scalable | +| May 31, 2025 | ASP.NET Core Identity | Built-in, well-tested, integrates with EF Core | +| May 31, 2025 | 24-hour token expiration | Balance between security and UX | + +### Technical Notes +- Store only password hash, never plain text +- Use HttpOnly cookies for refresh tokens if possible +- Sanitize username/email inputs to prevent injection +- Rate limit login attempts to prevent brute force + +### Gotchas +- ⚠️ JWT secret must be long and random (>32 characters) +- ⚠️ Token must be stored securely on frontend (HttpOnly cookie or secure localStorage) +- ⚠️ CORS must be configured to accept credentials if using cookies +- ⚠️ Password hashing should use work factor appropriate for your hardware + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Application Plan: [Application Plan](../architecture/application-plan.md) +- Reference: [ASP.NET Core Identity Docs](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) +- Reference: [JWT in .NET](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/jwt/) + +--- + +*Feature created from application-plan.md* diff --git a/docs/features/vocabulary-system.md b/docs/features/vocabulary-system.md new file mode 100644 index 0000000..f27485a --- /dev/null +++ b/docs/features/vocabulary-system.md @@ -0,0 +1,368 @@ +# Feature: Vocabulary System + +> **Status**: ⏳ Planned +> **Priority**: High +> **Complexity**: High +> **Estimate**: 8-12 hours +> **Assignee**: - +> **Created**: May 31, 2025 +> **Target Completion**: - +> **PR**: - +> **Related Features**: Infrastructure Setup, Lesson Management, AI Services (TTS) + +--- + +## 📌 Overview + +### Purpose +Implement a comprehensive vocabulary management system that includes word storage, retrieval, audio generation, and integration with lessons. + +### User Story +As a learner, I want to study German vocabulary with translations, articles (der/die/das), and audio pronunciations so that I can build my word knowledge effectively. + +### Acceptance Criteria +- [ ] Vocabulary words can be created, read, updated, and deleted (Admin) +- [ ] Each word has: German text, English translation, article (optional), audio URL +- [ ] Vocabulary is associated with specific lessons +- [ ] Words can be filtered by lesson, level, or source (Goethe/DW) +- [ ] Audio is generated for each word using Coqui TTS +- [ ] Users can practice vocabulary through various exercises + +--- + +## 📋 Requirements + +### Functional Requirements +| ID | Requirement | Priority | +|----|-------------|----------| +| FR-001 | CRUD operations for vocabulary words | High | +| FR-002 | Associate words with lessons | High | +| FR-003 | Store article information (der/die/das) | High | +| FR-004 | Generate audio for each word | High | +| FR-005 | Import vocabulary from Goethe Institut | High | +| FR-006 | Import vocabulary from DW Learn German | High | +| FR-007 | Filter vocabulary by lesson/level/source | Medium | +| FR-008 | Vocabulary exercises (flashcards, matching) | Medium | +| FR-009 | Admin bulk import functionality | Medium | + +### Non-Functional Requirements +- Performance: Vocabulary listing < 100ms +- Storage: Audio files ~50-100KB per word +- Security: Only admins can create/edit/delete words +- Data Integrity: Word-article combinations must be valid + +--- + +## 🏗️ Technical Design + +### Components Involved +- **Backend**: VocabularyController, VocabularyService, VocabularyImportService +- **Database**: Vocabulary table +- **Models**: Vocabulary, VocabularyDto +- **External**: Coqui TTS for audio generation +- **Frontend**: VocabularyTab, VocabularyCard, VocabularyExercise components + +### Data Flow +``` +1. Admin imports vocabulary from Goethe/DW +2. System scrapes word list from source +3. For each word: + a. Store in Vocabulary table + b. Generate audio using Coqui TTS + c. Save audio file and update AudioUrl +4. User views lesson vocabulary +5. Frontend displays words with audio playback +6. User can click to hear pronunciation +``` + +### API Endpoints +| Endpoint | Method | Description | Auth Required | +|----------|--------|-------------|----------------| +| `/api/vocabulary` | GET | List all vocabulary (with filters) | Yes | +| `/api/vocabulary/{id}` | GET | Get specific vocabulary word | Yes | +| `/api/vocabulary` | POST | Create new vocabulary word (Admin) | Yes | +| `/api/vocabulary/{id}` | PUT | Update vocabulary word (Admin) | Yes | +| `/api/vocabulary/{id}` | DELETE | Delete vocabulary word (Admin) | Yes | +| `/api/vocabulary/lesson/{lessonId}` | GET | Get vocabulary for a lesson | Yes | +| `/api/vocabulary/import` | POST | Import from Goethe/DW (Admin) | Yes | +| `/api/vocabulary/audio/{id}` | GET | Get audio file for word | Yes | + +### Database Schema (from application-plan.md) +```sql +CREATE TABLE Vocabulary ( + Id SERIAL PRIMARY KEY, + LessonId INT REFERENCES Lessons(Id) ON DELETE CASCADE, + Word VARCHAR(50) NOT NULL, + Translation VARCHAR(100) NOT NULL, + Article VARCHAR(10) CHECK (Article IN ('der', 'die', 'das', '')), + AudioUrl VARCHAR(255), + ImageUrl VARCHAR(255), + Source VARCHAR(50) CHECK (Source IN ('Goethe', 'DW')) +); +``` + +### Vocabulary Word Structure +```csharp +public class Vocabulary +{ + public int Id { get; set; } + public int LessonId { get; set; } + public string Word { get; set; } // e.g., "Buch" + public string Translation { get; set; } // e.g., "book" + public string? Article { get; set; } // "der", "die", "das", or null + public string? AudioUrl { get; set; } // URL to audio file + public string? ImageUrl { get; set; } // Optional image URL + public string Source { get; set; } // "Goethe" or "DW" +} +``` + +--- + +## 🚀 Implementation Plan + +### Phase 1: Database & Models (2 hours) +- [ ] Create Vocabulary entity +- [ ] Create VocabularyDto for API responses +- [ ] Create VocabularyRepository interface +- [ ] Create VocabularyRepository implementation +- [ ] Create migration for Vocabulary table +- [ ] Add vocabulary to Lesson entity (one-to-many relationship) + +### Phase 2: Core CRUD Operations (2-3 hours) +- [ ] Create VocabularyService with basic CRUD +- [ ] Create VocabularyController +- [ ] Implement filtering (by lesson, level, source) +- [ ] Add validation for word data +- [ ] Add authorization (Admin for write operations) +- [ ] Write unit tests for VocabularyService + +### Phase 3: Audio Generation (2-3 hours) +- [ ] Integrate with Coqui TTS service +- [ ] Create audio generation queue/background job +- [ ] Configure audio storage location +- [ ] Implement audio file serving endpoint +- [ ] Add audio URL to vocabulary DTOs +- [ ] Create migration to add AudioUrl column + +### Phase 4: Vocabulary Import (2-3 hours) +- [ ] Create VocabularyImportService +- [ ] Implement Goethe Institut scraper +- [ ] Implement DW Learn German scraper +- [ ] Create bulk import endpoint +- [ ] Add validation for imported words +- [ ] Generate audio for imported words +- [ ] Create admin UI for import (optional) + +### Phase 5: Frontend Integration (1-2 hours) +- [ ] Create VocabularyTab component +- [ ] Create VocabularyCard component with audio playback +- [ ] Create VocabularyExercise component +- [ ] Add vocabulary to LessonPage + +### Milestones +| Milestone | Date | Status | +|-----------|------|--------| +| Database & Models | - | ⏳ | +| Core CRUD | - | ⏳ | +| Audio Generation | - | ⏳ | +| Vocabulary Import | - | ⏳ | +| Frontend Integration | - | ⏳ | + +--- + +## ✅ Tasks + +### Backend +- [ ] Create Domain/Entities/Vocabulary.cs +- [ ] Create Application/DTOs/VocabularyDto.cs +- [ ] Create Domain/Interfaces/IVocabularyRepository.cs +- [ ] Create Infrastructure/Data/Repositories/VocabularyRepository.cs +- [ ] Update Lesson entity to include Vocabulary collection +- [ ] Create Application/Services/VocabularyService.cs +- [ ] Create Presentation/Controllers/VocabularyController.cs +- [ ] Create VocabularyImportService +- [ ] Create endpoints for audio serving +- [ ] Integrate with Coqui TTS service +- [ ] Register services in Program.cs +- [ ] Write unit tests +- [ ] Write integration tests + +### Database +- [ ] Create migration for Vocabulary table +- [ ] Add foreign key to Lessons table +- [ ] Add indexes for LessonId, Source +- [ ] Apply migration + +### Audio Generation +- [ ] Set up Coqui TTS configuration +- [ ] Create audio file storage directory +- [ ] Implement audio generation for new words +- [ ] Implement background job for bulk audio generation +- [ ] Create audio file cleanup mechanism + +### Vocabulary Import +- [ ] Research Goethe Institut vocabulary structure +- [ ] Research DW Learn German vocabulary structure +- [ ] Implement web scraping for Goethe +- [ ] Implement web scraping for DW +- [ ] Create bulk import API endpoint +- [ ] Add rate limiting to scrapers + +### Frontend +- [ ] Create components/VocabularyTab.tsx +- [ ] Create components/VocabularyCard.tsx +- [ ] Create components/VocabularyExercise.tsx +- [ ] Create hooks/useVocabulary.ts +- [ ] Integrate with LessonPage +- [ ] Add audio playback functionality + +--- + +## 🔗 Dependencies + +### Feature Dependencies +- [Infrastructure Setup](infrastructure-setup.md) - Required +- [Lesson Management](lesson-management.md) - Required (vocabulary associated with lessons) +- [AI Services - TTS](ai-services.md) - Required (for audio generation) + +### Technical Dependencies +- Coqui TTS Python library +- HTML Agility Pack or similar for web scraping +- AutoMapper (optional) + +### Blockers +- [ ] Infrastructure Setup must be complete +- [ ] Lesson Management must be complete for vocabulary-lesson association +- [ ] Coqui TTS service must be configured + +--- + +## ✅ Definition of Done + +### General Criteria (All Features) +- [ ] All acceptance criteria met and verified +- [ ] All tasks in this document completed +- [ ] Code follows Clean Architecture principles +- [ ] Code reviewed and approved by at least 1 team member +- [ ] All tests passing (unit, integration) +- [ ] Documentation updated (README, AGENTS.md if applicable) +- [ ] Feature works in development environment +- [ ] Feature deployed to staging environment +- [ ] Performance meets defined targets +- [ ] Security review completed +- [ ] No critical bugs or blockers + +### Vocabulary-Specific Criteria +- [ ] Vocabulary words can be created, read, updated, and deleted +- [ ] Each word has: German text, translation, article, audio URL +- [ ] Words are correctly associated with lessons +- [ ] Words can be filtered by lesson, level, source +- [ ] Audio is generated for all vocabulary words +- [ ] Audio files are accessible and playable +- [ ] Goethe Institut vocabulary import works +- [ ] DW Learn German vocabulary import works +- [ ] Vocabulary exercises (flashcards, matching) are functional + +--- + +## 🧪 Testing Strategy + +### Testing Approach + +| Test Type | Coverage | Tools | Responsibility | +|-----------|----------|-------|----------------| +| Unit Tests | 80%+ code coverage | MsTest, Moq | Backend Dev | +| Integration Tests | All service interactions | MsTest, TestContainers | Backend Dev | +| API Tests | All endpoints | MsTest, HttpClient | Backend Dev | +| Frontend Unit Tests | Component logic | Vitest | Frontend Dev | +| Frontend Integration | Service integration | Vitest | Frontend Dev | +| E2E Tests | Critical user journeys | Playwright | QA/Dev | +| Manual Testing | Exploratory, edge cases | BrowserStack | QA | + +### Vocabulary-Specific Tests + +#### Backend Tests +- [ ] Create word with valid data → success +- [ ] Create word with missing required fields → error +- [ ] Create word with invalid article → error +- [ ] Create word with invalid source → error +- [ ] Get word by ID → returns correct word +- [ ] Get words by lesson → returns correct list +- [ ] Get words by level → returns correct list +- [ ] Get words by source → returns correct list +- [ ] Update word → success +- [ ] Update word with invalid data → error +- [ ] Delete word → success +- [ ] Bulk import from Goethe → creates words correctly +- [ ] Bulk import from DW → creates words correctly +- [ ] Audio generation for word → creates audio file + +#### Audio Tests +- [ ] Audio file generated for new word +- [ ] Audio file accessible via endpoint +- [ ] Audio file is valid WAV format +- [ ] Audio file quality is acceptable +- [ ] Audio file size is within limits + +#### Integration Tests +- [ ] Create lesson with vocabulary → both created +- [ ] Get lesson → includes vocabulary +- [ ] Delete lesson → cascades to vocabulary +- [ ] Import vocabulary → audio generated for all words + +--- + +## 📝 Notes & Decisions + +### Decisions Made +| Date | Decision | Rationale | +|------|----------|-----------| +| May 31, 2025 | Generate audio for all vocabulary | Essential for pronunciation practice | +| May 31, 2025 | Store audio files on filesystem | Simple for MVP, can migrate to CDN later | +| May 31, 2025 | Import from Goethe and DW | High-quality, trusted sources | +| May 31, 2025 | Include article information | Critical for German language learning | + +### Technical Notes +- Audio files should be stored with consistent naming: `/audio/vocabulary/{id}.wav` +- Vocabulary table has CHECK constraint for Article (only der/die/das or empty) +- Vocabulary table has CHECK constraint for Source (only Goethe or DW) +- Consider adding phonetic transcription (IPA) in the future +- Audio generation can be resource-intensive - consider queueing for bulk operations + +### Gotchas +- ⚠️ Coqui TTS may have issues with some German words - need fallback mechanism +- ⚠️ Web scraping may break if Goethe/DW change their HTML structure +- ⚠️ Audio files can be large - consider compression +- ⚠️ Need to handle duplicate words across different lessons +- ⚠️ Article may be empty for verbs, adjectives, etc. + +### Article Rules Reference +- **der**: Masculine nouns (e.g., der Mann, der Tag) +- **die**: Feminine nouns (e.g., die Frau, die Stadt) +- **das**: Neuter nouns (e.g., das Kind, das Haus) +- **empty**: Verbs (e.g., gehen, sein), adjectives, adverbs, prepositions + +--- + +## 📊 Progress History + +| Date | Status Change | Notes | +|------|---------------|-------| +| May 31, 2025 | Created | Initial plan based on application-plan.md | + +--- + +## 📎 Related Files & Links + +- Architecture: [Backend Structure](../architecture/backend-structure.md) +- Architecture: [Application Plan](../architecture/application-plan.md) +- Database Schema: [Initial Database Schema](../database/initial-database-schema.sql) +- Feature: [Lesson Management](lesson-management.md) +- Feature: [AI Services](ai-services.md) +- Reference: [Goethe Institut Vocabulary](https://www.goethe.de/en/spr/ueb.html) +- Reference: [DW Learn German Vocabulary](https://learngerman.dw.com/en/learn-german/s-9528) +- Reference: [Coqui TTS](https://github.com/coqui-ai/TTS) + +--- + +*Feature created from application-plan.md* diff --git a/german-app-frontend/.gitignore b/german-app-frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/german-app-frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/german-app-frontend/README.md b/german-app-frontend/README.md new file mode 100644 index 0000000..7dbf7eb --- /dev/null +++ b/german-app-frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/german-app-frontend/eslint.config.js b/german-app-frontend/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/german-app-frontend/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/german-app-frontend/index.html b/german-app-frontend/index.html new file mode 100644 index 0000000..74e26bc --- /dev/null +++ b/german-app-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + german-app-frontend + + +
+ + + diff --git a/german-app-frontend/package-lock.json b/german-app-frontend/package-lock.json new file mode 100644 index 0000000..f79faae --- /dev/null +++ b/german-app-frontend/package-lock.json @@ -0,0 +1,2768 @@ +{ + "name": "german-app-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "german-app-frontend", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", + "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", + "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.0", + "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/german-app-frontend/package.json b/german-app-frontend/package.json new file mode 100644 index 0000000..0aeb1a2 --- /dev/null +++ b/german-app-frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "german-app-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } +} diff --git a/german-app-frontend/public/favicon.svg b/german-app-frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/german-app-frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/german-app-frontend/public/icons.svg b/german-app-frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/german-app-frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/german-app-frontend/tsconfig.app.json b/german-app-frontend/tsconfig.app.json new file mode 100644 index 0000000..7f42e5f --- /dev/null +++ b/german-app-frontend/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/german-app-frontend/tsconfig.json b/german-app-frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/german-app-frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/german-app-frontend/tsconfig.node.json b/german-app-frontend/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/german-app-frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/german-app-frontend/vite.config.ts b/german-app-frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/german-app-frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +})