- JWT_SECRET fails fast at startup in production
- CORS fails closed if APP_URL + CORS_ORIGINS are both missing
- Explicit HSTS (1y, includeSubDomains, preload)
- Rate limit sensitive auth endpoints (change-password, 2FA)
- /api/health now returns {ok:true}; details gated behind admin auth
- Login enumeration removed — generic 401 + dummy bcrypt on miss
- ReDoS guard: 20KB input cap on /suggest-codes
- showToast uses textContent, no innerHTML
- clearSession() clears service worker caches on logout
- OIDC state is now HMAC-signed and stateless (survives restart)
- SSRF guard on admin-set OIDC issuer (blocks private IPs, requires HTTPS)
Adds docs/mobile-build.md covering APK build, release, git push,
keystore, and troubleshooting for both PedScribe and PedsHub apps.
Web still uses localStorage; Capacitor native app now routes
token/user/session-id through capacitor-secure-storage-plugin
(iOS Keychain, Android EncryptedSharedPreferences / Keystore).
A thin SecureStorage wrapper detects Capacitor at runtime and
falls back to localStorage elsewhere, keeping a single auth.js
codebase for both targets.
To activate on mobile: cd mobile && npm install && npx cap sync android
New feature: after generating any clinical note, the app automatically
suggests relevant billing codes displayed as clickable chips below the output.
Backend (src/routes/billing.js):
- POST /api/suggest-codes endpoint analyzes note text
- Extracts diagnoses from Assessment section via regex
- Looks up ICD-10 codes: local common pediatric map (40+ conditions)
first, then NLM Clinical Tables API for unknown terms
- Suggests CPT E/M codes based on note type, visit complexity,
ROS/PE system counts, and MDM level estimation
- Supports: outpatient (new/established), well visit (age-based),
ED, inpatient (admit/subsequent/discharge)
Frontend (public/js/app.js):
- suggestBillingCodes() renders collapsible card with ICD-10 and CPT chips
- Click any chip to copy the code to clipboard
- Shows E/M level assessment (diagnosis count, ROS, PE, MDM complexity)
- Disclaimer: "Suggestions only. Always verify codes."
Integration: called after note generation in all 6 tabs
(encounter, SOAP, sick visit, well visit, hospital course, chart review)
- Fix: DELETE all other sessions query used empty string fallback when
req.sessionId was undefined, causing id != '' to match ALL rows
(including current session). Now skips deletion if sessionId unknown.
- Fix: Revoke All endpoint returns error if current session not identified
- Fix: var confirm shadowing window.confirm in password change handler
- Replace browser prompt() with inline UI for: 2FA disable (password field),
admin password reset (inline input), admin test email (inline input)
- Remove all password breach warning UI (login, register, settings)
Backend HIBP check endpoint remains but is no longer called from frontend
- Remove model cost display from dropdown and header badge
- Hide empty cost-badge element in header
- Fix model dropdown to flat list (no category grouping)
Security:
- Add session management: users can view/revoke active sessions in Settings
- Add password change in Settings (requires current password, HIBP check)
- Force logout all sessions on password reset
- Fix logout to destroy server-side session (was only clearing cookie)
- Add trust proxy for correct client IP in rate limiting and audit logs
- Add CORS support for multiple domains (CORS_ORIGINS env var)
- Add HIBP breach check endpoint and inline warnings on password fields
Audit logging:
- Add audit logging to all 24 PHI-handling endpoints across 13 route files
- Covers: generation, transcription, TTS, refine, encounters, documents, Nextcloud
- All fire-and-forget (no response delay)
AI improvements:
- Refine now includes original source material (transcript, notes, labs)
so AI can reference the full input when modifying output
- Add correction tracking (trackAIOutput) to sick visit and well visit tabs
- Fix sickvisit missing from encounter save noteIdMap
UI fixes:
- Non-blocking busy bar for transcription and AI generation (replaces full-screen overlay)
- Fix encounter recording: hide record button during recording (was showing two stop buttons)
- Fix ROS/PE "All WNL" stacking duplicate event handlers; add Clear buttons
- Enlarge AI instructions textarea in Learning Hub CMS
Domain:
- Primary domain now app.pedshub.com, with scribe.pedshub.com and peds.danvics.com as CORS origins
- Add Cloudflare Turnstile to login, register, and password reset forms
- Switch AI provider to LiteLLM, transcription to OpenAI Whisper
- Change domain to scribe.pedshub.com
- Fix PPTX export: add tables, bold/italic, numbered lists, code blocks, blockquotes
- Fix announcement banner close button (CSP was blocking inline onclick)
- Fix auth middleware: empty Bearer token now falls through to cookie auth
- Fix audio backups: only save on transcription failure, stop auto-deleting on success
- Soften AI correction injection to prevent model hallucination from correction history
- Fix LiteLLM TTS model name handling (no incorrect openai/ prefix)
- Expand AI instructions textarea in Learning Hub CMS
- Update README for v6 with all features and providers
- Add comprehensive docs/: architecture, API reference, database schema,
authentication, AI providers, speech, learning hub, configuration, deployment
- Add GET /api/transcribe/status endpoint — returns whether any server
transcription provider (Whisper/AWS/Local) is configured
- Frontend checks status on login via checkTranscribeStatus()
- When no provider configured: recording stops instantly, keeps live
Web Speech API text, shows friendly toast — no error, no upload wait
- Works in encounter, dictation, and SOAP tabs
- App now works fully out-of-the-box with just an AI provider key
- OIDC callback now passes only ?sso=ok flag, token stays in
httpOnly cookie (prevents token leaking to logs/referrer/history)
- Frontend auth.js uses cookie-based auth for SSO flow
- Add [PHYSICIAN TEMPLATES] boundary markers around physicianMemories
in all 5 generation routes to mitigate prompt injection
- Consistent boundary format across wellVisit, sickVisit, hpi, soap,
hospitalCourse
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
Login flash:
- Auth screen now hidden by CSS default (#auth-screen{display:none !important})
- auth.js shows it only when there is no valid token or token is expired
- No more flash of login page on refresh for logged-in users
Presentation quiz option (AI Generate panel):
- Presentation type now shows optional quiz question toggle (same as article)
- Backend: when questionCount>0, returns JSON {marpMarkdown, questions[]}
instead of plain Marp markdown
- applyAiContent fills both the Marp textarea and question blocks for presentations
Viewer revert:
- Removed the viewer presentation card change (user wants markdown as-is)
- Removed dangling viewer button wiring
Feed/CMS list:
- Presentation type shows correct icon (fa-presentation-screen) and
label "Slides" in the CMS content list
REVERT: httpOnly cookie auth removed — reverts to JWT in localStorage.
The cookie implementation broke the login page (always-hidden auth screen).
localStorage token approach is restored fully:
- Login/register return token in JSON body
- getAuthHeaders() sends Authorization: Bearer header
- Session check reads localStorage token on load
- clearSession() removes localStorage token
Fix: slide preview padding — section elements now have 40px 48px padding
so text is not flush against the frame edges.
Kept from v3.14: AI panel context-aware options (word count, slide count,
quiz question toggle), delete confirmation wording per content type.
AI Generate panel:
- Content type synced from editor on open
- Article/Pearl: optional word count field
- Presentation: optional slide count field
- Questions row: checkbox toggle for article/pearl (optional), always on for quiz,
hidden for presentation; count input shown only when enabled
- Backend: passes wordCount/slideCount to prompt; questionCount=0 skips questions
Delete confirmation:
- Wording built dynamically from content type
- Presentation: no mention of questions
- Quiz: "All quiz questions will be permanently removed"
- Article/pearl: "Any quiz questions attached will also be removed"
Auth refactor — JWT → httpOnly cookie:
- Backend: setAuthCookie() sets ped_auth as httpOnly, Secure, SameSite=Lax, 7d
- Backend: clearAuthCookie() on POST /api/auth/logout (new endpoint)
- Backend: auth middleware reads ped_auth cookie first, falls back to Bearer header
- Login/register: no longer return token in JSON body; cookie is the session
- Frontend: getAuthHeaders() returns only Content-Type (no Authorization header)
- Frontend: session check uses /api/auth/me via cookie (no localStorage token)
- Frontend: clearSession() calls /api/auth/logout to clear cookie server-side
- Frontend: no token in localStorage; window.AUTH_TOKEN removed
- All FormData fetches use credentials:'same-origin' (cookie sent automatically)
- index.html: optimistic auth-screen hide (removed localStorage token check)
Tested: login, logout, /api/auth/me, admin routes, FormData uploads, cookie httpOnly
flag, 401 without cookie, cookie cleared on logout.
Quill tooltip: move tooltip element to document.body via patchQuillTooltip()
so overflow:hidden on .cms-main cannot clip it. Recalculates position from
container-relative to viewport (fixed) coordinates with viewport clamping.
Removes overflow:hidden from quill wrappers; applies border-radius to toolbar/container.
Registration: hide #show-register by default in HTML; reveal only after
registration-status fetch confirms enabled=true. Endpoint already returns 403
when disabled — no backend change needed.
- New dedicated Content Manager tab (WordPress-like layout) with:
- Stats dashboard (published, drafts, categories, quizzes, attempts)
- Sidebar with category tree and filter controls (status, category)
- Table-style content list with search
- Full-page editor with title bar, meta fields, body editor, quiz builder
- Content Manager visible only for admin and moderator roles in sidebar
- Removed CMS from admin panel (admin panel is admin-only again)
- Email templates: fully responsive with max-width, proper mobile breakpoints,
table-based layout for Outlook/Gmail compatibility, no overflow
- Security: escape all user data in emails with escHtml(), escape APP_URL
in verify-email HTML, replace raw err.message with generic errors in auth
- Security: email body from admin settings now HTML-escaped before rendering
- 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
- Fix 2FA status stuck at "Loading...": export load2FAStatus globally so
app.js tabChanged handler can call it when navigating via sidebar
- Fix admin models silent failure: add error message + retry button on catch
- Fix save encounter doubles: persist _savedEncId_* to sessionStorage so
page refresh reuses existing record instead of inserting a duplicate;
add _savingInProgress guard to prevent race-condition double-saves
- Desktop sidebar collapsible: add collapse/expand buttons (not collapsed
by default), state saved to localStorage; hidden on mobile
- Copy to Visit Note now also copies SSHADESS assessment to wv-shadess-text
- Store selected visit age in window._wellVisitAge + sessionStorage so
other tabs can read the patient's age
- Bigger screening/vaccine boxes: min-height on wv-vax-list, wv-screen-list,
wv-visit-detail; slightly more padding on vaccine items
- Wire New Patient (clearTab) buttons to document click handler
- Replace horizontal tab-nav with collapsible left sidebar (sticky desktop, slide-in mobile with hamburger toggle)
- Fix tab persistence: use localStorage instead of URL hash; restore last tab after login in enterApp()
- Fix admin CMS models loading: move autoDeleteDays read inside .then() callback (was ReferenceError), switch admin loading from click-only to tabChanged event
- Add Save/Load bars to Hospital Course and Chart Review tabs with popover support
- Add wv (well visit) save handler in encounters.js
- Register load handlers for hospital and chart tabs
- Expand popover prefix list to include hosp and chart