Mobile touch fix:
- Added touch-action: manipulation and -webkit-tap-highlight-color: transparent
to .question-card .option CSS — prevents mobile browsers from interfering
with tap events (double-tap zoom, highlight flash, delayed clicks)
Documents hidden for regular users:
- Dashboard: stats card hides Documents count for non-moderators
- Dashboard: document list section completely hidden for non-moderators
- Documents API not called for non-moderators (saves a request)
Timer improvements:
- Custom timer input on ModeSelectScreen — set any number of minutes
for exam mode (overrides the quiz default, or add time to untimed quizzes)
- timeLeft saved to Redis progress every 1.5s (alongside answers, mode, voice)
- On resume: timer restarts from the saved remaining time
- Timer auto-submits when it reaches 0 (already worked, now also works on resume)
- Progress bar shows remaining time relative to what was left at resume
Nextcloud settings — server-side storage:
- GET /auth/me/settings — load user settings from Redis
- PUT /auth/me/settings — save settings to Redis (linked to user_id)
- SettingsPage loads Nextcloud config from server on mount
- Saves to BOTH server (cross-browser persistent) and localStorage (for UploadPage)
- Clear function wipes both server and localStorage
- Switching browsers and logging in restores Nextcloud settings
Also fixed: saved.attemptId → saved.attempt_id in ModeSelectScreen resume check
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Security:
- Nginx: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, CSP, Referrer-Policy, Permissions-Policy headers
- Redis-backed login rate limiting (survives container restarts)
- Admin litellm/models endpoint: api_key moved from GET query param to POST body
- Nextcloud credentials moved from localStorage to sessionStorage (cleared on tab close)
UX / Layout:
- Login: unverified users see inline "Resend verification email" option
- QuizPage mobile: TTS Listen button on its own row below question text
- QuizPage mobile: Voice selector on its own row in header card (not squashed with timer)
- QuizEditPage: scroll position preserved after saving a question edit
Quiz Categories:
- New QuizCategory model + quiz_categories table
- category_id column added to quizzes table
- GET/POST/DELETE /api/categories endpoints
- Quizzes grouped by category in QuizzesPage; moderators can assign via 🏷 menu
- Uncategorized section shown when categories exist
Question Bank:
- GET /api/questions/bank — search all questions across quizzes
- POST /api/questions/from-bank — create new quiz from selected questions (copies, originals untouched)
- QuestionBankPage: search, checkbox select, study modal, create quiz form
- "Question Bank" link added to Navbar
Search:
- "View all N questions →" button expands to full question list
- Each question has a Study button opening in-place modal with study mode
- Summary view shows 2 questions per quiz with Study button
Extraction prompt:
- Stronger emphasis on correct_answer field with step-by-step letter → full text example
- Explicit instruction never to store just the letter
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- FastAPI backend with JWT auth, roles (admin/moderator/user)
- PDF upload (up to 500MB) with streaming, PyMuPDF text extraction
- ChromaDB vectorization per page with metadata
- LiteLLM AI question extraction from PDF (not generation)
- Image extraction from PDF pages, graceful fallback
- Quiz modes: timed (countdown timer) + learning (answers shown inline)
- Page-by-page question navigation with dot navigator
- TTS endpoint using LiteLLM (Google Vertex / OpenAI voices)
- Admin dashboard: AI model management per task, user role management
- Moderator role: upload PDFs, create sections, generate quizzes
- Spaced repetition reminders via SMTP email (SM-2 intervals)
- APScheduler daily reminder jobs
- Celery + Redis for background PDF processing
- React frontend with all pages
- Docker Compose deployment (nginx + backend + celery + redis)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>