Adds a 6th feature bullet (with NEW badge) and a 7th icon tile for the
pediatric calculators + bedside emergency reference that ships with the
Pediatric AI Scribe app. Mentions weight-based dosing, sepsis, status
epilepticus, RSI, burns, anaphylaxis pathways.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously sent ?category_id= (empty string) which FastAPI rejects
with 422 validation error when parsing as int.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restore original single-column 600px layout
- Keep documents abridged to 5 most recent with 'Show all' button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ai_decide now samples 4 points across the section (start, 1/3, 2/3, end)
instead of just the first 30 + last 20 pages. This gives accurate strategy
detection on large documents where the answer format might be deeper in.
New ai_answer extraction mode:
- Extracts questions from Q&A-format PDFs that have no answer key
- AI picks the correct option from each question's choices
- Generates explanation using document context + medical knowledge
- Useful for PDFs like practice tests where answers were never included
- Available manually and as an ai_decide strategy
Flashcard decks can now be renamed:
- PATCH /flashcards/{deck_id} updates title
- Inline edit on FlashcardsPage with responsive layout (input full-width,
buttons wrap under it so Cancel never overflows the card)
- Title truncates with ellipsis when not editing
Note: generate mode (textbook -> MCQs) is unchanged per user request.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Suspend now pauses the timer instead of letting it run out:
- 'Suspend & Leave' sends suspended=true with time_left to backend
- On resume, backend re-anchors started_at to now with held time_left
- Closing tab without suspending continues to run the timer (unchanged)
Timer-expired auto-submits are marked with expired=1 and excluded from:
- Attempt history (GET /attempts/history)
- Dashboard stats (quiz count, total attempts, average score)
- Attempt list (GET /attempts)
- DDL: ALTER TABLE quiz_attempts ADD COLUMN expired INTEGER DEFAULT 0
Course-quiz decoupling is preserved — these changes only touch
non-course quizzes (Quiz.course_id IS NULL).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local uploads appeared stuck at 0% because onUploadProgress
doesn't fire granularly on fast local networks or when network
buffering makes the browser report instant completion.
Changes:
- Animated indeterminate progress bar when percent unavailable
- Elapsed seconds counter so user sees activity
- Separate "Uploading" vs "Processing on server" stages
- Hint message about large PDFs taking time
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creators preview courses without enrolling, so markComplete and SCORM
onComplete were hitting the progress endpoint and getting 403. Now
guarded by enrollment check — buttons hidden and callbacks skipped
for non-enrolled users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SCORM runtime data (bookmarks, scores, lesson position) now saved to
Redis via GET/PUT /courses/{id}/lessons/{id}/scorm/data endpoints.
Data loads before iframe renders and saves on Commit/Finish/Terminate.
90-day TTL on stored data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SCORM content expects window.API (1.2) or window.API_1484_11 (2004)
on the parent frame. Without it, content fails to initialize.
Implements all 8 required methods for both SCORM versions:
- Initialize/LMSInitialize, Terminate/LMSFinish
- GetValue/LMSGetValue, SetValue/LMSSetValue
- Commit/LMSCommit, error methods
- Auto-marks lesson complete when content reports completion
- Removed iframe sandbox to allow parent frame API access
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Certificate: PDF generated on course completion, download from course page
- SCORM: upload ZIP packages as lesson type, served in iframe, manifest parsed
- QTI 2.1: export selected/all questions as XML, import QTI files into bank
- Uses fpdf2 for certificate PDF generation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add list-docs, fix-stuck-docs, reprocess-doc CLI commands
- Create ADMIN.md with deployment, CLI, database, and troubleshooting docs
- Add HIBP warning to Settings page password change
- Remove separate Account nav link (already in Settings)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fully decouple course quizzes from main quiz system (hidden from
dashboard stats, history, search, results page)
- Course quiz results show "Back to Course" instead of retake/delete
- Add allow_review toggle for course creators to control answer review
- Show quiz title on course page, hide pool size from students
- Add course thumbnails to browse cards
- Replace passlib with bcrypt directly (compatible with existing hashes)
- Add HIBP breached password warnings on register/reset/change password
- Add CLI management tools (reset-password, set-role, stats, etc.)
- Fix quiz PATCH endpoint: ownership check instead of moderator-only
- Add max_length validation on course/module/lesson titles
- Fix score display bug on results page (0 of N when review disabled)
- Fix question count on course quiz start (show per-attempt, not pool)
- Improve suspend warning for timed course quizzes with max attempts
- Clean up validation error messages (show "Invalid email" not Pydantic dump)
- Add DDL migration for allow_review column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove category, thumbnail_url references (not on Course model)
- Fix sort_order → position (matching actual column name)
- Fix CourseCreate schema: only title + description
- Fix tab labels: "My Created" → "Created by Me"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LMS / Course System:
- Course → Module → Lesson hierarchy (any user can create)
- Lesson types: text, video (Vimeo/YouTube/local), document, quiz, live_session
- Video provider auto-detection from URL
- BBB API integration (create/join meetings) with env config
- Course enrollment with per-lesson progress tracking
- AI content generation/refinement for text lessons
- Draft/published/archived status workflow
- Subscription gate placeholder (requires_subscription flag)
- Thumbnail upload support
- Module/lesson reorder with up/down controls
User Quiz Creation:
- Any user can create quizzes from question bank (was moderator-only)
- User quizzes: is_published=0, is_shared=0 (private by default)
- Fixed section_id fallback: None instead of hardcoded 1
Manual Question Creation:
- POST /questions/create endpoint for manual MCQ entry
- CreateQuestionModal on QuestionBankPage with options, radio for correct answer
- Auto-generates embedding on creation
Frontend:
- CoursesPage: Browse/My Courses/Created tabs with search and pagination
- CourseDetailPage: Student view with module accordion, lesson viewer, progress
- CourseEditorPage: Full course builder with AI generate, question bank browser
- Courses link in Navbar
- Create Question button on Question Bank (available to all users)
Backend:
- 5 new tables: courses, course_modules, course_lessons, course_enrollments, course_lesson_progress
- Course model + schemas + router (22 endpoints)
- BBB_SERVER_URL + BBB_SECRET config
- Updated CLAUDE.md with LMS documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move GET /shared before GET /{deck_id} so FastAPI matches it correctly
(was being caught by /{deck_id} and failing int parse on "shared")
- Remove duplicate /shared endpoint left after move
- Fix corrupted middot character (U+FFFD) in My Decks card count display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Users can share/unshare their flashcard decks (toggle button)
- Shared tab: paginated list of all shared decks sorted by avg rating
- Star rating system (1-5): click stars to rate, updates average live
- Each user can rate each shared deck once (upsert on re-rate)
- Admin can force-unshare any deck (remove from shared listing)
- Shared decks are study-able by anyone (read-only access)
- Owner name displayed on shared deck cards
- Load more button for shared decks (12 per page)
- New DB table: flashcard_deck_ratings (user_id+deck_id unique)
- New column: flashcard_decks.is_shared (default 0)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix: flashcard task used wrong Redis key prefix (job: vs extraction:)
causing progress to stay on "pending" after completion
- Flashcards are now user-scoped: each user sees only their own decks
- Soft-delete decks: DELETE moves to trash, ?permanent=true to destroy
- Trash tab on flashcards page: restore or permanently delete decks
- Delete individual cards with inline confirmation in browse view
- Card edit/delete now allowed for deck owner (not just moderators)
- ExtractionProgress label prop: shows "Generating Flashcards" not
"Extracting Questions" for flashcard jobs
- Added deleted_at column to flashcard_decks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CLAUDE.md: comprehensive AI synopsis for working on the codebase
(architecture, critical rules, common patterns, what not to do)
- Fix ExtractionProgress showing "Extracting Questions" for flashcards
— now shows "Generating Flashcards" with correct navigation on done
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New feature: generate flashcards from PDF sections using AI, completely
separate from the existing quiz system.
Backend:
- FlashcardDeck + Flashcard models with cascade deletes
- flashcard_tag_links table for tag classification (reuses question_tags)
- /api/flashcards/ router: CRUD for decks, browse/search cards, tag filtering
- generate_flashcard_deck Celery task with chunked processing + progress
- FLASHCARD_PROMPT in extraction_modes.py (15 cards per chunk)
- "flashcard" added to admin model task types
Frontend:
- FlashcardsPage: deck grid + card browser with search/filter
- FlashcardStudyPage: flip cards, mark known/review, keyboard nav,
shuffle, progress bar, completion screen
- DocumentDetailPage: "Create Flashcards" button alongside "Extract Quiz"
- Navbar: Flashcards link
- AdminPage: flashcard in model task dropdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- App.jsx: simplify to single Routes block with Outlet layout pattern — fixes 404
- App.jsx: /home always accessible regardless of auth state
- LandingPage: show Dashboard/Quizzes/Question Bank nav links + Go to App CTA when logged in
- LandingPage: hero shows app navigation buttons when signed in
- LandingPage: fix apostrophe JSX syntax error in textarea placeholder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- teach.py, questions.py: replace f-string SQL with parameterized CAST(:vec AS vector) queries
- auth.py: add reusable check_rate_limit() Redis helper
- teach.py: rate limit /chat to 30 req/10min per user
- tts.py: rate limit /speak to 60 req/hr per user
- teach.py: stronger system prompt — no clarifying questions, use markdown, answer directly
- TeachChat.jsx: render assistant messages with ReactMarkdown (already in package.json)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- teach.py: use _proxy_model() + pass api_key/api_base from settings (fixes LiteLLM provider error for openrouter/bedrock models)
- teach.py: accept model_id in ChatRequest so frontend can select model
- main.py: remove titan-embed-v2 from general seed, auto-delete legacy entry on startup
- main.py: kill stale idle-in-transaction DB connections at startup to prevent DDL lock hangs
- main.py: set lock_timeout=10s on DDL connection as fast-fail safety net
- TeachChat.jsx: fetch /teach/models, show selector dropdown in header when >1 model available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Embedding:
- Embedding model now configurable via Admin UI (More tab) or LITELLM_EMBEDDING_MODEL env
- Calls LiteLLM proxy directly via httpx (bypasses LiteLLM library param validation)
- Passes dimensions=1024 to proxy; Redis setting overrides env var
- Default model: ge-gemini-embedding-001 (Gemini AI Studio, 1024-dim)
- Test button in admin UI to verify model works
- Fixed vector_service to use httpx + Redis model (was broken with non-prefixed model names)
Polly:
- Global enable/disable toggle in Admin → More settings (stored in Redis)
- /tts/voices filters out polly/* when disabled
- /tts/speak rejects polly requests when disabled
Job cancellation:
- POST /quizzes/job/{job_id}/cancel endpoint
- Cancel button on JobsPage for running jobs
- Celery task checks Redis status at each chunk boundary and exits cleanly
- Fixes DB lock on restart caused by cancelled jobs leaving open transactions
Admin UI:
- Settings tab renamed to "More" (heading: More Settings)
- Model row overflow fixed (minWidth: 0 + ellipsis on model_id)
- Embedding model search shows all proxy models (no auto-filter by "embed")
- Navbar correctly excludes cancelled/failed jobs from "extracting" count
README:
- Added Rebuild & Restart section with commands
- Updated embedding model reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: setQuizMode(mode) was called BEFORE the network fetch completed.
React immediately re-rendered showing the quiz, but quiz.questions still had
the initial load data (without correct_answer for study mode). On fast desktop
networks the fetch completed before the user could tap; on slower mobile
networks the user tapped an option while correct_answer was still null,
making every answer appear incorrect.
Fix: setQuizMode(mode) moved to AFTER setQuiz(quizRes.data) and
setAttemptId(). The ModeSelectScreen stays visible while data loads,
then switches to the quiz view only when all data (including
correct_answer) is ready.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
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>
Root causes:
1. joinedload(Quiz.questions) was SLOWER (129ms vs 75ms) with the custom
secondary relationship — removed, back to lazy load
2. Redis progress could have stale attempt_id from a completed submission:
GET /attempts/progress now validates the attempt exists and is not completed
before returning; stale entries are auto-deleted
POST /attempts/{id}/submit now clears Redis progress server-side on submit
3. Navbar jobs polling was running every 4s even with no active jobs,
causing unnecessary requests — now polls every 30s when idle,
every 3s when jobs are actively running
Also cleared 3 stale Redis progress entries that were pointing to
already-completed attempts (which caused silent submit failures).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Extraction modes (no restart needed — code ready for next Celery deploy):
- New QuizCreate.extraction_mode field: standard|questions_only|two_step|regex|ai_decide
- extraction_modes.py: independent implementations that don't touch standard path
- questions_only: extract Q+options, correct_answer="PENDING" for manual fill
- two_step: separate answer key section scan + phase1/2/3 matching
- regex: AI detects answer pattern, generates regex, applies to full doc
- ai_decide: AI reads samples from start+end and picks strategy
- DocumentDetailPage: Extraction Mode dropdown with description per mode
- quiz_tasks.py: routes to correct mode, standard path completely unchanged
Database:
- Deleted 11 orphaned questions from PREP 2013 extraction (quiz 12 was already deleted)
- 268 questions remaining (all PREP 2012)
UI fixes:
- Nextcloud section in Settings now only shown to moderators/admins
(regular users can't upload PDFs so they don't need Nextcloud)
- Upload PDF already hidden in navbar for non-moderators (confirmed correct)
- Resume quiz: now async — study mode quiz data loaded BEFORE showing quiz
so correct_answer is available immediately for feedback
- Resume saves and restores voice selection
- voice field added to ProgressSave schema and Redis storage
- Progress save dependency includes selectedVoice
Attempts:
- POST /attempts/start: reuses existing incomplete attempt by default (fresh=false)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
In-progress quizzes section (Quizzes page):
- Shows all incomplete (not submitted) attempts in an amber ⏸ panel
- Each shows quiz title, start date, question count
- Resume button → navigates to quiz (which loads saved answers from Redis)
- Delete button (ConfirmButton) → deletes attempt + clears reminders
Quiz navigation warning:
- beforeunload handler warns when closing browser tab/window mid-quiz
- Leave confirmation modal when navigating away via React Router
(shows "Your progress is saved — resume later" + Stay/Leave buttons)
Quiz progress:
- Scroll on Next/Previous REMOVED per user request — page no longer scrolls
- Progress saved to Redis on every answer/navigation (1.5s debounce)
- Works across browsers, survives logout
- ModeSelectScreen loads saved progress from API and shows Resume button
Attempt management:
- POST /attempts/start now REUSES existing incomplete attempt (no more
creating duplicate incomplete attempts on every quiz start)
Pass fresh=true to force a new attempt
- GET /attempts/in-progress — list incomplete attempts for current user
- DELETE /attempts/{id} — delete attempt + reminders
Cleaned up 10 duplicate incomplete attempts from the database
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>