- Raise express.json limit from 1MB to 10MB — handles large chart reviews
with many notes (50 full clinic notes ≈ 600KB, well within new limit)
- Client-side: warn user if payload >8MB, friendly toast if >30 notes
- Bump to v13.0.0
- TTS response now includes X-TTS-Provider header (google-tts, litellm/model, elevenlabs)
- Frontend reads header and shows actual provider in toast instead of hardcoded "Adam/ElevenLabs"
- CORS exposes X-TTS-Provider header so frontend can access it
- Updated .env.example: clarify that LITELLM_TTS_MODEL and LITELLM_STT_MODEL
can be either the model_name alias OR the full provider/model path depending
on your LiteLLM config (important for BAA compliance routing)
- TRANSCRIBE_PROVIDER=litellm routes audio to LiteLLM /audio/transcriptions
- TTS_PROVIDER=litellm routes to LiteLLM /audio/speech
- Both auto-detect when LITELLM_API_BASE is set (no extra config needed)
- LITELLM_STT_MODEL (default: whisper-1), LITELLM_TTS_MODEL (default: tts-1)
- LITELLM_TTS_VOICE (default: alloy) — alloy/echo/fable/onyx/nova/shimmer
- ElevenLabs still works if ELEVENLABS_API_KEY is set and TTS_PROVIDER=elevenlabs
- Health endpoint now reports tts provider
- Add Vertex AI provider (Gemini models via @google-cloud/vertexai SDK)
- Add LiteLLM proxy support (OpenAI-compatible, routes to any provider)
- Admin panel: model search/discover from provider API, enable/disable, custom models, set default
- New endpoints: /config/models/discover, /config/models/add-discovered, /config/models/default
- Updated models.js with VERTEX_MODELS and LITELLM_MODELS lists
- Updated health endpoint with vertex + litellm status
Audio Backups:
- New audio_backups table in PostgreSQL (bytea, gzip compressed)
- POST /api/audio-backups — upload with gzip compression (level 6)
- GET /api/audio-backups — list user's backups
- GET /api/audio-backups/:id/audio — download decompressed audio
- DELETE /api/audio-backups/:id — delete backup
- Auto-cleanup every hour (24h expiry)
- Frontend saves to server first, falls back to IndexedDB
- Settings shows source badge (server/local) per backup
AI Corrections:
- Corrections list is now expandable — click to view original vs corrected
- Shows red "Original" and green "Corrected to" sections
- Click arrow to expand/collapse each correction
- Date shown on each correction
- APK: Add WAKE_LOCK, BOOT_COMPLETED, ACCESS_NETWORK_STATE permissions
- APK: Disable allowBackup for medical data security
- APK: AudioRecordingService now acquires wake lock, has stop action in notification
- Serve /.well-known/assetlinks.json for TWA domain verification
- Service worker: cache app shell, stale-while-revalidate for assets, network-first for API
- Admin model management: validate model ID format, prevent built-in conflicts, audit toggle actions, prevent disabling all models
- Bump version to v9.0.0, Docker tag to v9
- Full model management UI in admin panel: toggle models on/off, add custom
model IDs (any OpenRouter/Bedrock ID), set admin-configured default model
- /api/models now returns admin-set default model, frontend respects it
- Toggle switch CSS for clean enable/disable UX
- Backend already had the API endpoints, this adds the missing UI
OIDC/SSO:
- New /api/auth/oidc route with PKCE for secure authorization
- Supports Azure AD, Okta, Keycloak, PocketID, Google, any OIDC provider
- Admin configurable: issuer, client ID/secret, button label
- Option to disable local auth (force SSO only)
- Auto-creates users on first SSO login, links existing by email
- SSO button on login page, hidden until admin enables OIDC
Firefox:
- Show info toast on first recording that live preview requires
Chrome/Edge; server-side transcription still works in all browsers
pdf-parse (was v2, broken API):
- Downgraded to v1.1.1 — default export is a function again
- pdfParse is not a function error fixed
WebDAV file selection (style.display bug, same cascade issue):
- lh-ai-webdav-selected had inline style="display:flex" always visible
- selectWebdavFile() / deselectWebdavFile() now use element.style.display
- On select: browser div hides, selected indicator shows with file name + X
- On deselect (X): indicator hides, browser shows again
Topic context for Upload and Nextcloud tabs:
- Both tabs now have optional "Topic / context" field
- Sent to backend as 'topic' param to help AI focus the generated content
Inline refine bar (replaces window.prompt):
- Refine Body button shows/hides an amber inline input bar below generate buttons
- Enter instructions, press Apply — no browser dialog, works in all contexts
CSP: remove unsafe-inline from scriptSrc (security issue #2):
- Converted all 26 onclick= handlers in component HTML files to
data-action / data-target / data-label attributes
- Added delegated click handler in app.js for copy/speak/nc-export actions
- script-src now 'self' only; script-src-attr 'none'
WebDAV path endpoint (security issue #5):
- New POST /api/user/webdav-path (authMiddleware only, not moderator)
- nextcloud.js updated to use new endpoint
nodemailer: already at 6.10.1 (vulnerability fixed).
Backend (src/routes/learningAI.js):
- POST /api/admin/learning/ai-generate
* Accepts: topic text, uploaded file (PDF/TXT/MD/HTML), or Nextcloud WebDAV path
* Extracts text from PDFs via pdf-parse; plain text read directly
* Fetches WebDAV files using stored Nextcloud credentials
* Prompts AI to return structured JSON: title, subject, body (HTML), questions[]
* Strips code fences, falls back to regex JSON extraction if needed
- POST /api/admin/learning/ai-refine — refine existing body with instructions
- GET /api/admin/learning/webdav-browse — PROPFIND-based Nextcloud file browser
- POST /api/admin/learning/webdav-path — save user's default browse path
Frontend:
- AI Generate button in CMS editor header (gradient purple→blue)
- Panel with 3 source tabs: By Topic / Upload File / Nextcloud
- Drag-and-drop file dropzone with DataTransfer API support
- WebDAV file browser: navigate folders, select files, breadcrumb path
- Options: question count, content type (article/quiz/pearl), model selector, instructions
- Refine Current Body: prompt-driven AI rewrite of existing content
- Generated content auto-fills title, subject, type, Tiptap body, quiz questions
Settings:
- Nextcloud section: "Learning Hub Default Browse Path" field (shown when connected)
- Saves to webdav_learning_path column via /api/admin/learning/webdav-path
DB: ALTER TABLE users ADD COLUMN IF NOT EXISTS webdav_learning_path TEXT
Only serve index.html for GET /; all other unknown paths return HTTP 404
with a custom branded error page (stethoscope SVG illustration, matches
app design, links back to /).
- New Learning Hub under Pediatric menu with content feed, category browsing,
search, content viewer, and interactive quizzes (MCQ, true/false)
- Quiz system with per-option wrong-answer explanations and general explanations
- Admin CMS for creating/editing categories, content (articles, pearls, quizzes),
and inline question builder with answer options
- 3-role system: admin (full access), moderator (can manage Learning Hub content),
user (all clinical features + learning hub read access)
- 5 new database tables: learning_categories, learning_content, learning_questions,
learning_options, learning_progress
- User progress tracking with score history per quiz
- Moderators see "Content Manager" tab instead of full Admin panel
- Wrap save-bar + popover in .save-bar-wrap (position:relative) so the
absolute-positioned popover anchors correctly below the Load button
(previously popovers were outside the save-bar, so had no positioned
ancestor and appeared at the wrong place on screen)
- Remove confirm() dialog from deleteEncounter — encounter disappears
immediately from the popover list after deletion
- ICD-10 diagnoses: replace static-only lookup with live NLM Clinical
Tables API search (clinicaltables.nlm.nih.gov) with 280ms debounce;
results appear in a dropdown below the search input; Enter/click selects
- Add clinicaltables.nlm.nih.gov to CSP connect-src
- Drop unused position:relative from .save-bar (now on .save-bar-wrap)
Helmet 8 adds script-src-attr: 'none' by default which blocks ALL
inline event attribute handlers (onclick, onchange, etc.) regardless
of script-src unsafe-inline. This silently broke every onclick button:
copy, read aloud, nextcloud upload, generate, record, etc.
- CSP connectSrc was blocking service worker re-fetches to cdnjs.cloudflare.com
causing Font Awesome icons to go blank (settings, logout, nextcloud buttons)
- Add cdnjs, googleapis, gstatic, google to connectSrc to fix icon rendering
and Chrome Web Speech API (which connects to Google servers for TTS voices)
- Add vendor model Opus 4.6 and vendor model Sonnet 4.6 to all three provider lists
(OpenRouter, Bedrock, Azure-compatible); Sonnet 4.6 now default for Bedrock
- Enable Helmet CSP (scripts/styles/fonts/connect restricted to self + CDNs)
- Lock CORS to APP_URL origin in production (open in dev)
- Reduce JSON body limit from 50mb to 1mb
- Add per-route rate limits: login 10/15min, register 5/hr, forgot-pw 5/hr
- Add README with full setup, Docker Hub, provider switching, env reference
- Bump version to 3.0.0