Commit graph

226 commits

Author SHA1 Message Date
Daniel
6dffdf91e5 Sliding 24h idle timeout (web) + persistent mobile + 2FA backup codes
Session model:
  Web     — 24h sliding idle timeout enforced server-side via
             user_sessions.last_activity. 30-day JWT + cookie are a
             safety net; middleware is the real clock. Cookie is
             re-set on active use so browsers match the sliding window.
  Mobile  — 365-day JWT, no idle timeout (stays persistent via Keychain
             / Keystore). Detected via User-Agent ("PedScribe" /
             "Capacitor") or X-Client: mobile header.

2FA backup codes:
  - 10 single-use codes generated when 2FA is first enabled
  - Stored as bcrypt hashes in new users.totp_backup_codes column
  - Consumed atomically on successful login fallback (when TOTP fails)
  - Regenerate endpoint (POST /api/auth/2fa/backup-codes) requires
    current password; invalidates prior codes
  - Count endpoint (GET /api/auth/2fa/backup-codes/count) powers a
    "N codes remaining" indicator on the 2FA settings card
  - Modal shows codes exactly once with Copy + Download .txt actions
  - Codes cleared when 2FA is disabled

New files:
  src/utils/platform.js — isMobileClient() helper

Schema migration (idempotent):
  ALTER TABLE users ADD COLUMN IF NOT EXISTS totp_backup_codes TEXT
2026-04-14 04:24:54 +02:00
Daniel
b294150781 Session lifetime: 7 days → 24 hours
Shortens both JWT expiresIn and httpOnly cookie maxAge to 24h in
auth.js (local + register + reset flows) and oidc.js (SSO callback).

Rationale: shorter absolute session window for a PHI-adjacent app.
No sliding idle refresh — user re-logs in once a day.
2026-04-14 04:17:54 +02:00
Daniel
cdf178b1c3 Mobile app hardening — security + Android 14 compat
capacitor.config.json:
  - webContentsDebuggingEnabled: true → false
    (was leaving Chrome DevTools able to attach to released builds)
  - allowMixedContent: true → false
    (API is HTTPS-only; no need to permit cleartext loads)
  - server.allowNavigation: ["*"] → restricted to pedshub.com /
    peds.danvics.com origins
    (prevents WebView following an attacker-controlled redirect)

AndroidManifest.xml:
  - android:allowBackup="false" + data_extraction_rules.xml
    (Android system backup would otherwise copy EncryptedSharedPreferences
     containing the auth token into Google Cloud backups)
  - Removed USE_BIOMETRIC permission (feature removed earlier)

AudioRecordingService.java:
  - startForeground(id, notif, TYPE_MICROPHONE) on Android 14+
    (without the explicit type Android 14 kills the service with
     MissingForegroundServiceTypeException)
  - WakeLock cap: 1h → 8h (still bounded, onDestroy releases early)

MainActivity.java:
  - Removed dead biometric code path and androidx.biometric imports

mobile/package.json:
  - Dropped @aparajita/capacitor-biometric-auth — orphan dependency
2026-04-14 04:15:27 +02:00
Daniel
fa16cb13cb Hybrid auth: cookie-only on web, Keychain Bearer on mobile
Runtime split driven by window.Capacitor.isNativePlatform():

  Web browser
    - No token in localStorage / sessionStorage — XSS can't read it
    - Server-set httpOnly cookie carries the session
    - fetch() default credentials='same-origin' sends the cookie
    - getAuthHeaders() returns Content-Type only, no Authorization
    - Middleware already falls back to cookie when Bearer is absent

  Capacitor native (iOS / Android)
    - Unchanged — Bearer token lives in Keychain / Keystore via the
      capacitor-secure-storage-plugin SecureStorage wrapper
    - Bearer header still sent on every request

enterApp() / clearSession() / getAuthHeaders() all now branch on
isNativeApp(). Legacy localStorage entries from the dual-mode era
are wiped on clearSession() for users migrating in.

Rollback: git reset --hard pre-httponly-only-2026-04-14
2026-04-14 04:11:55 +02:00
Daniel
4a26abed10 Maintenance CLI + unpin postgres digest
Adds `npm run maint:check` (health report) and `npm run maint:reindex`
(REINDEX DATABASE + REFRESH COLLATION VERSION + ANALYZE) for post-
upgrade maintenance, modelled after Nextcloud's occ maintenance.
Documented in README.

Also relaxes postgres image from digest pin back to tag-pin
(pgvector/pgvector:pg16) — the auto-REINDEX-on-drift check in
database.js and the COLLATE "C" protection on critical indexes
make the digest pin redundant while blocking ordinary `compose
pull` updates.
2026-04-14 04:00:13 +02:00
Daniel
d748dcc0d2 Pin critical auth indexes to COLLATE "C" (ICU-drift immune)
idx_users_email and idx_sessions_token_hash now use byte-order
collation so a future ICU library bump cannot silently corrupt the
indexes the way it did this week. The columns themselves retain
their default collation; only the index comparison is C, which is
safe for these because:

  - users.email is lowercased ASCII in practice
  - user_sessions.token_hash is SHA-256 hex (pure ASCII)

Both are used for equality lookups only, never ORDER BY. Migration
is idempotent, gated on app_settings.migration.text_indexes_c.

Slug indexes on learning_* tables left at default for now — those
are also ASCII in practice but under lighter load; the startup
drift check + auto-REINDEX covers them.
2026-04-14 03:55:09 +02:00
Daniel
b23cb3300e Collation-drift guard + lookup-miss visibility
Root cause of recent "invalid credentials on correct password" was
a silent btree index corruption: pgvector/pgvector:pg16 was pulled
with a different ICU library than the one used to build existing
indexes. Queries returned 0 rows even though matching heap rows
existed. Postgres logged nothing (corrupt index → empty result set
is a "successful" query) and the login path never logged unknown-
user attempts (enumeration protection).

Three defenses:

  1. Pin postgres image by digest in docker-compose.yml so a
     silent pull can't change ICU under our feet.
  2. Startup collation-drift check in src/db/database.js:
     compares pg_database.datcollversion to the library's actual
     version and, on mismatch, runs REINDEX DATABASE + ALTER
     DATABASE REFRESH COLLATION VERSION. Logs "Collation versions:
     aligned" on clean boot.
  3. Server-side console.warn on login lookup-miss (no email, no
     audit row — preserves enumeration protection but gives
     Grafana/Loki a signal for unusual miss rates).
2026-04-14 03:52:29 +02:00
Daniel
9b407d1e18 Login: remove temporary debug logging
Root cause for "invalid credentials" on correct password was a
corrupt btree index (idx_users_email) causing user lookups to miss
existing rows. Fixed by REINDEX DATABASE. Keeping a typed catch
around passwords.verify() so any future verify throw is logged
cleanly instead of bubbling as 500.
2026-04-14 03:48:56 +02:00
Daniel
e283bb8cda Growth/BMI results: percentiles to 2 decimal places
Percentile displays in growth charts, BMI, and mid-parental height
now show 2 dp (e.g. "37.42th") instead of 1 dp ("37.4th") for more
precision at tail percentiles.
2026-04-14 03:37:40 +02:00
Daniel
13e8937a00 Growth chart: accept explicit 0 in any age field
Previously any form of zero total ("0 days", all blank) rejected
with "Enter age". Newborns at birth are a legitimate entry —
distinguish blank-all (error) from explicit-zero (valid).
2026-04-14 03:33:10 +02:00
Daniel
5dde108e4a Growth chart age: three boxes (yr/mo/day), any combination
Replace single text input with three number fields — years, months,
days — that all combine into fractional months. Fill any subset:
leave years blank for a newborn, leave months blank for "2 years",
enter just days for a 10-day-old.

Live hint below ("= 2 yr 5 mo (29 mo total)") still shows the
interpreted total. Parser from prior commit retained on window
for reuse elsewhere.
2026-04-14 03:21:27 +02:00
Daniel
42984e355b Growth chart: flexible age input with smart parser
Replace [years] + [months dropdown] with a single text field that
accepts:
  3y / 3 years / 3 yr
  29m / 29 months / 29 mo
  2y5m / 2 years 5 months / 2 yr 5 mo
  3.5 years / 36 (plain number = months)
  15 days / 2 weeks / 3y 2m 10d

Enables fractional ages so newborns can be plotted accurately
(WHO/CDC growth curves are continuous — "0 months" means at birth,
not a 0-27 day bucket, so a 15-day-old should plot at ~0.5 months).

Live hint below the field shows how the input was interpreted
("= 2 yr 5 mo (29 mo total)").
2026-04-14 03:17:16 +02:00
Daniel
3e05d8eec9 Dockerfile: add build tools for argon2 native compile
argon2 requires node-gyp + python3 + g++ + make to build its C
extension. Added as a virtual .build-deps package so it's compiled
during npm install, then purged to keep the Alpine image slim.
2026-04-14 03:09:38 +02:00
Daniel
9bfadd7344 Stop leaking e.message to clients across all routes
88 occurrences of res.status(500).json({ error: e.message }) (or
err.message) swept to generic 'Request failed'. Server-side
console.error / logger.error calls are untouched, so the full detail
still lands in logs and Grafana.

Covers: admin, adminConfig, adminMilestones, chartReview, documents,
encounters, hospitalCourse, hpi, learningAdmin, learningAI, learningHub,
logs, memories, milestones, oidc, refine, sessions, sickVisit, soap,
userPreferences, wellVisit.

Also extends .gitignore to exclude .env.backup-* files.
2026-04-14 03:04:24 +02:00
Daniel
cb17a12172 Security hardening: PHI encryption, argon2, DOMPurify, SRI
- App-layer AES-256-GCM crypto helper (src/utils/crypto.js)
- Nextcloud tokens encrypted at rest; transparent migration on next use
- Audio backups encrypted at rest (version byte 0x01 envelope); legacy
  rows still decrypt as-is until overwritten
- argon2id password hashing via src/utils/passwords.js with bcrypt
  fallback; bcrypt hashes rehashed to argon2id on next successful login.
  argon2 package is optional — server keeps running with bcrypt only
  until npm install adds the native dep
- PHI redactor for audit log details (src/utils/redact.js) — strips SSN,
  phone, email, DoB, long IDs; caps at 500 chars; detects note bodies
- DOMPurify (cdnjs, SRI-pinned) replaces custom regex sanitizer in
  Learning Hub content rendering
- SRI integrity hashes added for Font Awesome CSS and Chart.js
- Magic-byte file-type verification on document uploads
  (src/utils/fileType.js)
- Generic 500 error responses via src/utils/errors.js applied to
  nextcloud and audioBackups; full detail still logged server-side
- DATA_ENCRYPTION_KEY env documented in .env.example

Deploy: requires rebuild of the container image to pick up the new
files and `npm install` (adds argon2). Existing users keep working
because bcrypt stays available and crypto helpers pass through
plaintext when the key is not yet set in dev.
2026-04-14 02:49:38 +02:00
Daniel
93bc44b5e0 Security hardening: low-risk easy wins
- 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.
2026-04-14 02:42:32 +02:00
Daniel
8409a49c74 Add hardware-backed secure storage for mobile auth token
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
2026-04-14 02:33:32 +02:00
Daniel
942647871a Add APK download link on login page
Links to GitHub releases/latest for Android APK download.
2026-04-14 02:29:38 +02:00
Daniel
369e440aa1 Enhance audit logging: user agent, session ID, PHI access tracking
Loki logs now include:
- User agent string (browser/device identification)
- Session ID (ties actions to specific login session)
- Status field (success/failure)

New logging:
- encounter_load: logged when user opens a saved encounter (with label)
- copy_to_clipboard: logged when user copies note content (PHI access)
- Client event endpoint: POST /api/logs/client-event (auth required)

Encounter save/delete/load all include the encounter label for
patient identification in audit trail.

HIPAA audit trail now covers: who, what, when, from where, which
device, which session, what patient data, success/failure.
2026-04-11 06:17:05 +02:00
Daniel
0c8a4db5c3 Add full hour-by-hour exchange transfusion thresholds for all GA groups
Exchange transfusion data (AAP 2022) now covers GA 35, 36, and 38+ weeks
with and without risk factors, hour-by-hour from 12-96h (510 more data
points). Total bilirubin data: 1020 data points (6 photo + 6 exchange
tables x 85 hours each). No interpolation needed for any hour.
2026-04-11 06:03:48 +02:00
Daniel
30300f169c Bilirubin: full hour-by-hour AAP 2022 data (510 data points)
Replace interpolated thresholds with exact hour-by-hour values
extracted from PediTools API for every hour from 12-96h:
- 6 phototherapy tables (GA 35/36/38 x with/without risk factors)
- 85 data points per table = 510 total values
- No interpolation needed — exact AAP 2022 nomogram values
- Exchange transfusion thresholds for GA 38 (with/without risk)
2026-04-11 06:01:00 +02:00
Daniel
5d988c397d Update bilirubin to exact AAP 2022 values, add exchange transfusion
Phototherapy thresholds updated with exact values extracted from
PediTools (validated against AAP 2022 nomograms):
- Separate tables for GA 35, 36, and 38+ weeks
- With and without neurotoxicity risk factors
- Hour-specific values at 12, 24, 36, 48, 60, 72, 84, 96, 120h

Previous approximations were 1-3 mg/dL too low (conservative but
inaccurate). New values match the published AAP 2022 curves exactly.

Exchange transfusion thresholds added for GA 38+ weeks (with/without
risk factors). Displayed alongside phototherapy threshold in results.

GA selection expanded: 35, 36, 37, 38, 39, 40+ weeks.
Chart now shows both phototherapy and exchange transfusion lines.

Also: fixed Loki port conflict (3100->3101), added logs.pedshub.com.
2026-04-11 05:50:19 +02:00
Daniel
e700ab1c8b Add pause/stop buttons to SOAP note recording
- Add Pause and Stop buttons (hidden until recording starts)
- Record button hides during recording (same pattern as encounter)
- Pause: suspends MediaRecorder + speech recognition, shows Resume
- Resume: handles MediaRecorder state recovery if browser killed it
- Stop: triggers the record button's stop flow
- Native mobile: haptic feedback + keep-awake + foreground service
- Recognition respects pause state (doesn't restart during pause)
2026-04-11 05:19:12 +02:00
Daniel
6a690f6483 Add Glasgow Coma Scale calculator and equipment sizing reference
GCS Calculator:
- Child/Adult and Infant versions with toggle
- Eye opening (4), Verbal (5), Motor (6) dropdowns
- Auto-calculates total score with severity classification
  (Mild 13-15, Moderate 9-12, Severe/Coma 3-8)
- Infant-modified verbal and motor scales per Kids Kard
- Updates on every dropdown change (no button needed)

Equipment Sizing (Johns Hopkins Kids Kard):
- Select age/weight group (premie through 16+)
- Shows: BVM, oral/nasal airway, blade, ETT, LMA, Glidescope,
  IV catheter, central line, NGT/OGT, chest tube, Foley
- All values from Johns Hopkins Children's Center Kids Kard
- ETT formulas shown as reference
2026-04-11 05:01:57 +02:00
Daniel
d29f55f8a6 Increase API rate limit to 200 req/min (Turnstile errors were exhausting 60/min limit) 2026-04-11 04:47:08 +02:00
Daniel
04030b1ded Fix vital signs selector, add resuscitation medications calculator
Vital Signs:
- Fix age selector not responding (replaced setTimeout with event
  delegation on parent panel — works reliably with hidden panels)
- Update values to Johns Hopkins Kids Kard data (8 age groups:
  premie, 0-3mo, 3-6mo, 6-12mo, 1-3yr, 3-6yr, 6-12yr, >12yr)
- Each age group shows: HR awake/sleeping, RR, SBP, DBP, temp,
  SpO2, weight range, and clinical pearls

Resuscitation Medications (new calculator tab):
- Enter patient weight, calculates all 13 PALS medication doses
- Adenosine, Amiodarone, Atropine, Calcium Chloride/Gluconate,
  Dextrose (weight-based concentration), Epinephrine (arrest/anaphylaxis),
  Hydrocortisone, Insulin, Lidocaine, Magnesium, Naloxone, Bicarb
- Color-coded by category (cardiac/metabolic/reversal)
- Max dose capping, route, special notes per medication
- Source: Johns Hopkins Kids Kard / AHA PALS 2020
2026-04-11 04:42:23 +02:00
Daniel
bdf0916fe7 Fix vital signs age selector: add setTimeout for DOM readiness 2026-04-11 04:28:49 +02:00
Daniel
0630e460e8 Interactive vital signs selector with clinical notes per age group
Replace static vital signs table with interactive age group dropdown.
Each selection shows: HR (awake/sleeping), RR, SBP, DBP, temperature,
SpO2 target, weight range, and age-specific clinical notes.

10 age groups: preterm through 18 years. Values from Harriet Lane
Handbook 23rd Edition. Includes AAP 2017 BP classification thresholds
for ages 13+, ETT sizing formulas, and clinical pearls (orthostatic
testing, febrile tachycardia, athletic bradycardia, etc.).

Full reference table preserved as collapsible "View All Age Groups".
2026-04-11 04:19:39 +02:00
Daniel
64546a743d Remove biometric prompt (will implement properly with token-based auth later) 2026-04-11 03:57:40 +02:00
Daniel
baa6362d29 Native Android: biometric auth, foreground service bridge, mic fix
Major Android native improvements:

Biometric authentication:
- Native AndroidX BiometricPrompt on app launch (2nd launch onwards)
- Supports fingerprint, face, iris, and device PIN/password fallback
- Gracefully skips if no biometric hardware or first launch
- Uses SharedPreferences to track first launch

Microphone permission:
- Added MODIFY_AUDIO_SETTINGS permission (required for WebView audio)
- Added androidScheme: "https" in Capacitor config (getUserMedia requires
  secure context)
- WebChromeClient properly grants WebView permission after Android
  runtime permission is obtained
- Handles pending permission request across the async flow

Background recording bridge:
- NativeRecording JavaScript interface exposed to WebView
- startForegroundService() / stopForegroundService() callable from JS
- Web app calls these on recording start/stop in liveEncounter.js
- AudioRecordingService keeps CPU awake + shows notification when recording
- Recording survives screen lock via foreground service + wake lock

Also:
- USE_BIOMETRIC permission added to manifest
- androidx.biometric:biometric dependency added to build.gradle
- Haptic fallback to navigator.vibrate when Capacitor plugins unavailable
2026-04-11 03:42:23 +02:00
Daniel
7a957e856e Fix WebView mic: grant both Android runtime + WebView permissions
The WebView has its own permission layer separate from Android runtime
permissions. Both must be granted. Now when the web page requests mic
access, the WebChromeClient checks if Android permission exists, grants
the WebView request if yes, or requests Android permission first then
grants the pending WebView request in the callback.
2026-04-11 03:37:45 +02:00
Daniel
f03ca5cb94 Fix Android mic permission, simplify launcher, remove broken biometric
- MainActivity: request RECORD_AUDIO permission at app start via
  ActivityCompat (not WebChromeClient override which broke Capacitor bridge)
- Simplify launcher: remove server reachability check (was failing in
  WebView), just save URL and navigate directly
- Remove biometric auth from launcher (Capacitor plugins need ES module
  bundler, not available in plain HTML). Biometric can be added later
  via the web app with proper Capacitor runtime.
- Add webContentsDebuggingEnabled for development
2026-04-11 03:34:47 +02:00
Daniel
6978ed708c Fix Android: auto-grant WebView mic permission, match status bar color
- MainActivity: override WebChromeClient to auto-grant WebView
  permission requests (microphone, camera) so the Android runtime
  permission dialog shows instead of WebView silently denying
- Add colors.xml with PedScribe blue (#2563eb / #1d4ed8)
- Update styles.xml: set statusBarColor and navigationBarColor to
  match app theme (fixes brown/mismatched bar at top)
- Change base theme to NoActionBar (removes action bar)
2026-04-11 03:29:15 +02:00
Daniel
17a0371a0f Fix launcher: simplify server check for Android WebView compatibility
WebView blocks no-cors fetch and image probes differently than browsers.
Simplified to a normal fetch that treats CORS errors as 'server reachable'
(CORS error = server responded, just blocked the origin).
2026-04-11 03:23:00 +02:00
Daniel
7582e3563d Add Loki + Grafana monitoring stack, ntfy notifications, biometric auth
Monitoring:
- docker-compose.monitoring.yml — opt-in Loki + Grafana stack
- Loki config with 6-year retention (HIPAA compliant)
- Grafana auto-provisioned with Loki datasource + PedScribe dashboard
  (login activity, failed logins, clinical actions, API calls, log viewer)
- Logger ships to Loki in parallel with PostgreSQL (fire-and-forget)
- Labels: app=pedscribe, type=audit|api_call|access, category, action

Usage: docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d
Grafana at localhost:3003 (admin/pedscribe)

Notifications:
- ntfy push support (src/utils/notify.js)
- Notifications on: login, password change, registration
- Self-hosted, no Firebase dependency

Mobile:
- Biometric auth on app launch (Face ID/Touch ID/fingerprint)
- PIN/password fallback, auto-prompt, skip option
2026-04-11 03:00:52 +02:00
Daniel
d0d65446f6 Add biometric auth, ntfy push notifications, mobile improvements
Mobile:
- Add biometric authentication (Face ID/Touch ID/fingerprint) on app launch
  with PIN/password fallback, auto-prompts on launch, skip option
- Add @aparajita/capacitor-biometric-auth plugin

Backend:
- Add ntfy push notification support (src/utils/notify.js)
  Self-hosted, no Firebase dependency, uses user's existing ntfy instance
- Notifications for: new login, password changed, new registration (admin)
- Topic format: pedscribe-user-{id} for users, pedscribe-admin for admins
- Env: NTFY_URL, NTFY_TOKEN (optional)
2026-04-11 02:35:07 +02:00
Daniel
aa33a55d0b Mobile app: haptics, deep linking, share intent, push notifications, keep-awake
Native improvements:
- Add haptic feedback on recording start (heavy) and stop (medium)
- Add keep-screen-awake during recording (nativeKeepAwake)
- Add isNativeApp() detection helper
- Android: deep linking (pedscribe:// + https://app.pedshub.com)
- Android: share intent for text/plain and application/pdf
- iOS: deep linking (pedscribe:// URL scheme)
- iOS: remote-notification background mode
- Add Capacitor plugins: haptics, keyboard, push-notifications,
  screen-orientation, share

Updated README with complete build/deploy instructions,
App Store listing suggestions, and icon generation guide.
2026-04-11 02:28:30 +02:00
Daniel
d079c6d6c9 Fix mobile app bugs: package name, deprecated API, server check
- Fix AudioRecordingService ACTION_STOP to use com.pedshub.scribe
- Fix deprecated stopForeground(true) to STOP_FOREGROUND_REMOVE
- Fix launcher.js testServer: prevent double callback, fix onerror
  always reporting success (now correctly fails on unreachable servers)
- Update service comment from TWA to Capacitor
2026-04-11 02:22:32 +02:00
Daniel
bf586daf4d Add Capacitor native mobile app (PedScribe) for iOS + Android
New mobile/ directory with Capacitor project:
- Configurable server URL launcher (default: app.pedshub.com)
- Android: foreground service + wake lock for background recording
  (AudioRecordingService preserved from existing TWA)
- iOS: background audio mode + microphone permission
- App ID: com.pedshub.scribe
- Both platforms initialized and synced

Existing android/ TWA project untouched — this is a separate project.
Build: cd mobile && npx cap open android (or ios)
2026-04-11 02:18:06 +02:00
Daniel
ee88e51f14 Add automatic ICD-10 and CPT billing code suggestions
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)
2026-04-11 01:50:17 +02:00
Daniel
4fbfc913d0 Add pediatric calculators: BP, BMI, growth, bilirubin, vitals, BSA, dosing
Calculators tab with 7 tools:
- BP Percentile (AAP 2017) with age/sex/height classification
- BMI Percentile (CDC 2000) with extended obesity classification
  (Class 1/2/3 using % of 95th percentile per CDC 2022)
- Growth Charts: weight-for-age, length-for-age, head circumference,
  weight-for-length (WHO/CDC LMS), Fenton preterm (22-50 weeks)
- Bilirubin: AAP 2022 phototherapy threshold + Bhutani nomogram
  with Nelson Table 137.1 risk factors for severe hyperbilirubinemia
- Vital Signs by Age (Harriet Lane) with quick reference formulas
  (estimated weight, min SBP, ETT size, maintenance fluids 4-2-1)
- Body Surface Area (Mosteller formula)
- Weight-Based Dosing with max cap and volume calculation

Fix growth chart sub-tab navigation (pills scoped separately from
top-level nav to prevent panel disappearing)
2026-04-09 17:56:30 +02:00
Daniel
79994f4781 Replace all browser dialogs with modern modal, add OIDC admin UI
- Add reusable showConfirm() modal component (supports plain confirm,
  input prompt, danger styling, Enter key)
- Replace ALL 18 confirm() and prompt() calls across 8 JS files with
  showConfirm() modal: admin user actions, session revoke, document
  delete, template delete, milestone management, transcription settings
- Fix broken admin reset-password (btn was undefined in scope)
- Add OIDC/SSO configuration UI to Admin Panel (issuer, client ID/secret,
  button label, disable local auth toggle, callback URL display)
2026-04-09 02:43:23 +02:00
Daniel
adf1365fa2 Fix session revocation bug that could log out current device
- 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
2026-04-09 02:33:00 +02:00
Daniel
4fa2b58d75 Remove prompt() dialogs, breach warnings, cost display; fix 2FA disable UI
- 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)
2026-04-09 02:27:55 +02:00
Daniel
04f3aa56cb FAQ page, dep security patches, model dropdown and UI fixes
- Add FAQ tab with accordion sections: Getting Started, AI & Models,
  Voice & Transcription, Saving & Export, Privacy & Security,
  Well Visit & Sick Visit, Learning Hub, Troubleshooting
- Documents how AI learns from physician edits (correction tracker)
- Fix FAQ accordion (CSP was blocking inline script, moved to app.js)
- Patch all 5 npm vulnerabilities: nodemailer 8.0.5, xmldom, basic-ftp,
  path-to-regexp (npm audit now reports 0 vulnerabilities)
- Remove model category grouping from dropdown (flat list, no optgroups)
- Fix model dropdown dark background on options (white bg, dark text)
- Update FAQ model guidance to reflect admin-managed model selection
2026-04-09 01:56:11 +02:00
Daniel
020e831b3c v6.2: Session management, password change, audit logging, refine context, UI fixes
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
2026-04-08 20:27:45 +02:00
Daniel
a535ff6c15 Add developer guide, expand admin model management docs
- New docs/developer-guide.md: full walkthrough of frontend SPA architecture,
  backend middleware stack, database layer, AI integration, settings system,
  how to add features/routes/tables, key design decisions, file references
- Expand ai-providers.md: detailed admin model management (add custom models
  with ID/name/cost/category, discover from provider, enable/disable, set default)
- Update README docs index
2026-04-04 23:02:02 +02:00
Daniel
a36235c646 v6.1: Turnstile bot protection, LiteLLM provider, PPTX tables, audio backup fixes, docs
- 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
2026-04-04 22:56:24 +02:00
ifedan-ed
f98b9b7b71 feat: Add model search, testing, and TTS/STT/embedding management to admin
- Fix model search for all providers: Bedrock now falls back to built-in
  list (with live ListFoundationModels attempt), Azure returns built-in list
- Add Test button on every model row (built-in, discovered, custom) that
  sends a live prompt and shows response + latency in a toast
- Add TTS management section: search voices from provider API (Google TTS
  voices.list, LiteLLM /v1/models, ElevenLabs /v1/voices), Set as Default
  writes tts.voice/tts.model to DB, runtime respects DB override
- Add STT management section: search models from provider (Gemini, Whisper,
  LiteLLM, OpenAI, local), Set as Default writes stt.model to DB, runtime
  respects DB override in transcribe.js
- Add Embedding models section: search from provider (LiteLLM, Vertex,
  OpenAI), Set as Default writes embeddings.model+dimensions to DB,
  embeddings.js respects DB override
- Add record-and-transcribe STT test (browser MediaRecorder)
- Add TTS synthesize-and-play test (returns base64 audio)
- Add embedding generate test (shows dims + vector sample)
- Expand PUT /config/:key(*) whitelist to include tts., stt., embeddings.
- Add @aws-sdk/client-bedrock as optional dependency for live Bedrock discovery
2026-04-03 19:55:11 +00:00
ifedan-ed
4fb038a745 v2.2: Remove milestone admin UI, add CMS content refresh button
Some checks failed
Build TWA APK / build-apk (push) Has been cancelled
Build & Push Docker Image / build (push) Has been cancelled
REMOVED:
- Milestone editing UI from Admin Panel (per user request)
- Milestones will be managed via hardcoded static data only
- Kept backend routes and database support for future use

ADDED:
- Refresh button in Learning Hub CMS content list
- Manual refresh for AI-generated content updates
- Better discoverability of content refresh functionality

FIXES:
- AI learning content now has visible refresh button
- Users can manually refresh content list after AI generation
- Cleaner admin panel without milestone management clutter

NOTE:
- Developmental milestones still work via static fallback
- Edit milestones by modifying public/js/milestonesData.js
- Backend API still supports milestone management if needed later
2026-04-01 18:16:00 +00:00