Three concurrent themes from this session:
═══════════════════════════════════════════════════════════════════
ED ENCOUNTERS — per-stage cards + consolidate→MDM finalize
═══════════════════════════════════════════════════════════════════
UX redesign per Daniel's feedback ("every stage note should be shown,
if AI is told to modify that particular note then the modified version
is used in final mdm"):
- Each generated stage stays on screen as its own editable card with
its own embedded "Don't Miss" panel. No more single rolling note
element that gets replaced on each generation.
- gatherCurrentNotes() reads contenteditable text from each stage card
before any operation (advance, finalize, persist) so inline edits
flow into the next AI call and the final consolidate.
- Stage badge is now state-accurate. "Stage N (recording)" with yellow
background after Add-more before generation; "Stage N" with gray
after generation. Fixes the bug where the badge flipped to Stage 2
the moment Add-more was clicked.
- Save & Done now runs TWO server-side AI calls in /finalize:
1. edConsolidate (new prompt) → polished single final note that
integrates every stage chronologically (HPI / ROS / PE / ED Course /
A&P with disposition).
2. edFinalize (rewritten with full inline 2023 AMA E/M element
rubric — problems / data / risk definitions, level mapping with
concrete examples) → MDM JSON.
- Two new cards render after finalize: blue-bordered Final Consolidated
Note + green-bordered MDM. Stage cards become read-only.
- partial_data on the saved row now stores {stages, finalNote, mdm,
finalized} so resume re-renders the full state.
Why two-call finalize: a single combined prompt makes the model cut
corners on one task. Two focused calls cost ~2× latency at the very end
of an encounter — acceptable since finalize is a one-time terminal
action, not a per-stage hot path.
Files: public/components/ed-encounter.html, public/js/ed-encounters.js,
src/routes/edEncounters.js, src/utils/prompts.js (edConsolidate added,
edFinalize rewritten).
═══════════════════════════════════════════════════════════════════
EXTENSIONS / PAGERS — visual polish
═══════════════════════════════════════════════════════════════════
Multiple iterations based on Daniel's feedback:
- Layout: align-items:flex-start so action buttons stay pinned top-right
when long numbers wrap (was align-items:center → buttons drifted into
the text area, causing visible overlap).
- Number: word-break:break-all + min-width:0 + font-feature-settings:tnum
so long numbers wrap within their column instead of pushing under the
buttons. Click-to-copy with a 0.55s green flash + ✓ copied badge.
- Phone/pager Font Awesome icon next to the number in the type color —
at-a-glance type signal (replacing an earlier 3px left stripe that
Daniel found visually bulky).
- Name: font-weight 700, font-size 14.5px, color g900, letter-spacing
-0.012em — scan-target headline typography for long lists.
- Alternating subtle backgrounds by index (white vs #fafbfc) so a long
list reads as distinct rows.
- Hover: card lifts 1px with a soft shadow; action buttons fade from
55% to 100% opacity. Cubic-bezier transition on transform.
- Entrance: staggered fade-up animation per card (35ms × index, capped
at 12). prefers-reduced-motion media query disables motion.
- Empty state: 48px FA icon + heading instead of plain gray text.
Files: public/js/extensions.js, public/css/styles.css.
═══════════════════════════════════════════════════════════════════
DOCS REORGANIZATION + APPLICATION-LOGIC DOCS + ADMIN VIEWER
═══════════════════════════════════════════════════════════════════
Document moves (preserving git history via git mv):
BROWSER_WHISPER_SETUP.md → docs/browser-whisper-setup.md
BROWSER_WHISPER_TROUBLESHOOTING.md → docs/browser-whisper-troubleshooting.md
DEVELOPER_GUIDE.md → docs/developer-guide-extended.md
EMBEDDINGS_SETUP.md → docs/embeddings-setup.md
FEATURES_EXPLAINED.md → docs/features-explained.md
IMPROVEMENTS.md → docs/improvements.md
OPENID_SETUP.md → docs/openid-setup.md
TRANSCRIPTION_OPTIONS.md → docs/transcription-options.md
README.md updated with the new paths + a Documentation section that
links to docs/logic/ at the top.
New application-logic doc series (~8,300 lines total) at docs/logic/.
Built with 5 parallel doc-writing agents per Daniel's "use multiple
agents" directive. Each doc explains how a part of the app actually
works — application logic, data flow, design decisions, sacred zones,
how-to-extend recipes — at a depth that lets a new dev (or an AI
assistant) modify the code confidently.
docs/logic/README.md — index + recommended reading order
docs/logic/architecture.md (2166 L) — frontend IIFE pattern, lazy tab
load, backend route convention,
schema, encryption, deployment
docs/logic/clinical-notes.md (1546L) — every note tab + helper trio
docs/logic/bedside-and-calculators.md (1373L) — bedside ES module
pocket + calculators + PE Guide
+ suture selector
docs/logic/auth-admin-learning.md (1281L) — auth (local+OIDC+2FA) +
admin panel + Learning Hub
(Quiz engine logic at sub-detail
only — TODO follow-up)
docs/logic/ai-and-voice.md (1128 L) — callAI 5-provider routing,
prompts, voice/STT, helper trio
docs/logic/ed-encounters.md (821 L) — multi-stage ED + MDM (this
session's worked example)
Admin-only docs viewer:
- New route /api/admin/docs/{tree,file}: recursively walks docs/, returns
the tree as JSON; /file?path=X validates path stays inside docs/ and
renders markdown via marked. Both gated by req.user.role==='admin'.
- New tab "Docs" (book icon) in the sidebar, hidden by default and
revealed in auth.js when user.role==='admin' (same pattern as the
existing Admin and CMS tabs).
- New component public/components/admin-docs.html: split-pane layout
with a tree sidebar + filter input + a markdown reader pane.
- New module public/js/admin-docs.js: lazy-loads the tree on first tab
activation, renders collapsible folders, persists expanded state and
last-opened path via UIState. Server-rendered HTML so no client
markdown parser needed.
- CSS for the viewer (responsive split-pane, code-block styling, table
scrolling, etc.).
- Mounted at /api/admin/docs (NOT /api) — important: mounting a router
with router.use(authMiddleware) at /api accidentally 401s every other
/api/* path (caught and fixed during testing — /api/health was 401'ing).
Files: docs/* (moved + new), README.md, public/components/admin-docs.html
(new), public/js/admin-docs.js (new), src/routes/adminDocs.js (new),
public/index.html (tab + section + script), public/js/auth.js (admin
gate + logout cleanup), public/css/styles.css (viewer styles), server.js
(mount).
═══════════════════════════════════════════════════════════════════
KNOWN GAPS (TODO follow-ups)
═══════════════════════════════════════════════════════════════════
- Learning Hub quiz engine (MCQ / multi-select / T-F scoring + attempt
tracking + progress dashboard) is covered at the architectural level
in docs/logic/auth-admin-learning.md but not drilled into the quiz
data model and scoring flow. Worth a focused follow-up doc.
- ED finalize: if MDM step JSON parse fails, server returns 502 with
the consolidated finalNote in the error payload, but client doesn't
surface the partial result. Add a "MDM failed, retry" affordance.
- No e2e Playwright coverage for ED encounters or the new docs viewer.
11 KiB
Features Explained - Pediatric AI Scribe v14
🎙️ Audio Backups
How It Works:
Audio backups happen automatically every time you record, regardless of transcription success/failure.
Flow:
- You press "Stop" on recording
- Audio is immediately saved before transcription starts
- Server-side backup (PostgreSQL, gzip compressed) attempted first
- If server fails → fallback to browser IndexedDB
- After successful transcription → audio backup is deleted
- If transcription fails → audio backup remains for retry
Location:
- Server: PostgreSQL
audio_backupstable (auto-deleted after 24 hours) - Browser: IndexedDB
PedScribeAudioBackupdatabase (manual cleanup)
Purpose:
- Retry transcription if it fails
- Recover audio if browser crashes
- Audit trail (24 hour retention)
Access: Settings → Audio Backups section shows:
- Date/time of recording
- Module (encounter, dictation, etc.)
- File size
- "Retry Transcription" button (if transcription failed)
- "Delete" button
Cost: Server backups are compressed (gzip) to ~1/10 original size. A 2MB recording becomes ~200KB in database.
🌐 S3 Document Storage
How It Works:
Upload documents (PDFs, images, Word docs, text files) to S3-compatible storage.
Supported Providers:
- AWS S3 (default)
- Backblaze B2
- MinIO (self-hosted)
- Any S3-compatible service
Configuration (.env):
# AWS S3 (uses Bedrock credentials if available)
S3_BUCKET=your-bucket-name
S3_REGION=us-east-1
S3_PREFIX=documents/ # Optional: folder prefix
# Backblaze B2
S3_BUCKET=your-bucket-name
S3_ENDPOINT=https://s3.us-west-004.backblazeb2.com
S3_REGION=us-west-004
S3_ACCESS_KEY_ID=your-b2-application-key-id
S3_SECRET_ACCESS_KEY=your-b2-application-key
# MinIO (self-hosted)
S3_BUCKET=your-bucket
S3_ENDPOINT=http://minio:9000
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=minio-access-key
S3_SECRET_ACCESS_KEY=minio-secret-key
S3_FORCE_PATH_STYLE=true # Required for MinIO
Features:
- ✅ 10 MB file size limit
- ✅ AES-256 server-side encryption
- ✅ Per-user folder organization (
documents/{userId}/{uuid}/filename) - ✅ Metadata stored in PostgreSQL (filename, mime type, size, description)
- ✅ Presigned URLs for secure access (1 hour expiry)
Allowed File Types:
- PDF (
.pdf) - Images (
.jpg,.jpeg,.png,.gif) - Word documents (
.doc,.docx) - Text files (
.txt,.csv)
Access: Settings → Documents section
Status Check: If S3 is not configured, the Documents section shows empty with message: "S3 not configured"
📚 Learning Hub - Default Browse Path
What It Is:
A user preference that sets the starting folder when browsing Nextcloud files for AI content generation.
When It's Used:
Only in the Learning Hub AI Content Generator (Admin/Moderator feature).
Scenario:
- Admin/Moderator wants to create AI-generated learning content
- They choose "Upload from Nextcloud"
- File browser opens
- Instead of starting at root
/, it opens at the configured path
Example:
Default path: /Medical-Resources
↓
When you click "Browse Nextcloud", it opens:
/Medical-Resources/
├── Pediatric-Guidelines/
├── Clinical-Protocols/
└── Research-Papers/
Instead of:
/
├── Personal/
├── Photos/
├── Medical-Resources/ ← you'd have to navigate here every time
└── ...
Configuration: Settings → Nextcloud Integration → "Learning Hub — Default Browse Path"
Examples:
/Medical-Resources- Opens in Medical Resources folder/Shared/Clinical-Content- Opens in shared clinical content/(empty) - Opens at root (default behavior)
Who Can Use This:
- Any authenticated user (not just moderators)
- It's a personal preference per user
- Only affects Learning Hub AI file picker
Why This Exists: If you store learning resources in a specific Nextcloud folder, you don't want to navigate there every single time you generate content. Set it once, it remembers.
🎤 Browser Whisper Pre-Download
Issue You Reported:
"Pre-download models works, stuck at starting download"
What's Happening:
The download is actually working but progress updates are slow because:
- HuggingFace CDN serves large files (39-244 MB)
- Progress callbacks are not granular (reported per-file, not per-chunk)
- Initial ONNX runtime download has no progress tracking
Fixed:
- ✅ Added console logging to track progress
- ✅ Added 30-second timeout warning (doesn't stop download)
- ✅ Better error messages
How to Test:
- Open browser DevTools (F12) → Console tab
- Click "Pre-download model"
- Watch console for progress logs:
[BrowserWhisper] Starting preload... [BrowserWhisper] Progress: onnx-runtime 0% [BrowserWhisper] Progress: model.bin 23% [BrowserWhisper] Progress: model.bin 47% ... [BrowserWhisper] Progress: 100%
Expected Download Times:
- Tiny (39 MB): 5-15 seconds (fast connection)
- Base (74 MB): 10-30 seconds
- Small (244 MB): 30-90 seconds
If Still Stuck:
Check these:
- Open DevTools → Network tab
- Filter by "HuggingFace"
- Look for downloads from
cdn-lfs-us-1.huggingface.co - Check if files are actually downloading
Common issues:
- Slow internet connection (244 MB takes time!)
- Corporate firewall blocking HuggingFace CDN
- Browser IndexedDB quota exceeded
Workaround: Just enable it and record audio - the model will download on first use (same as pre-download, but triggered automatically).
🔊 TTS Voice Preview
Issue You Reported:
"Preview button next to TTS seems to do nothing"
Fixed:
- ✅ Added error logging to console
- ✅ Better validation (checks for empty selection)
- ✅ Clear user feedback messages
How to Use:
- Go to Settings → Voice Preferences
- Select a voice from "Text-to-Speech Voice" dropdown
- Click "Preview" button
- Wait 2-3 seconds
- Audio should play automatically
If Nothing Happens:
Check browser console for errors:
- Open DevTools (F12) → Console tab
- Click Preview
- Look for
[VoicePrefs] Preview error:message
Common issues:
- No voice selected → Select from dropdown first
- TTS not configured → Check
.envhasGOOGLE_VERTEX_PROJECTorLITELLM_API_BASE - Network error → Check server logs for TTS API errors
- Browser autoplay policy → Some browsers block autoplay, click page first
Testing Checklist:
# 1. Check TTS is configured
curl http://localhost:3000/api/health | grep tts
# 2. Test TTS endpoint directly
curl -X POST http://localhost:3000/api/text-to-speech \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{"text":"Test"}' \
--output test.mp3
# 3. Play the audio file
mpg123 test.mp3 # or open in browser
📋 Summary of User Settings
Voice Preferences
Location: Settings → Voice Preferences (top section)
| Setting | Options | Default | Purpose |
|---|---|---|---|
| STT Model | gemini-2.0-flash-exp, gemini-2.0-flash, gemini-1.5-flash, gemini-1.5-pro, whisper-1 | Server default | Controls transcription accuracy |
| TTS Voice | Journey-F/D, Studio-O/M, Neural2 series, alloy, echo, fable, onyx, nova, shimmer | Server default | Controls read-aloud voice |
Browser Whisper
Location: Settings → Browser Transcription (Local Whisper)
| Setting | Options | Default | Purpose |
|---|---|---|---|
| Enable | On/Off | Off | Local transcription (HIPAA-safe) |
| Model | Tiny, Base, Small | Tiny | Accuracy vs speed tradeoff |
Nextcloud
Location: Settings → Nextcloud Integration
| Setting | Purpose |
|---|---|
| Nextcloud URL | Your Nextcloud instance |
| Username | Nextcloud username |
| App Password | Generate in Nextcloud → Security |
| Default Browse Path | Starting folder for Learning Hub AI picker |
Documents (S3)
Location: Settings → Documents
Shows list of uploaded documents if S3 is configured. Upload limit: 10 MB per file.
Audio Backups
Location: Settings → Audio Backups
Shows last 24 hours of recordings. Can retry transcription or delete.
🔧 Troubleshooting Guide
Pre-Download Stuck
- ✅ Open browser console (F12)
- ✅ Look for
[BrowserWhisper] Progress:logs - ✅ Check Network tab for HuggingFace downloads
- ✅ Wait - 244 MB takes time!
- ✅ If truly stuck (no network activity): refresh page, try again
Preview Button Silent
- ✅ Check voice is selected in dropdown
- ✅ Open console for error messages
- ✅ Test TTS endpoint directly (curl command above)
- ✅ Check server logs for TTS provider errors
- ✅ Verify
.envhas TTS provider configured
S3 Not Working
- ✅ Check
.envhasS3_BUCKETset - ✅ Verify credentials:
S3_ACCESS_KEY_ID+S3_SECRET_ACCESS_KEY - ✅ Test bucket access from server:
aws s3 ls s3://your-bucket/ --region us-east-1 - ✅ Check server logs for S3 errors when uploading
Audio Backups Not Showing
- ✅ Record audio first (they're created on recording, not transcription)
- ✅ Check database:
SELECT COUNT(*) FROM audio_backups; - ✅ Verify IndexedDB in browser: DevTools → Application → IndexedDB →
PedScribeAudioBackup - ✅ Backups auto-delete after 24 hours
Learning Hub Path Not Working
- ✅ This only affects AI content generator file picker
- ✅ It does NOT affect manual Nextcloud document browsing
- ✅ Path must exist in your Nextcloud
- ✅ Path format:
/Folder/Subfolder(starts with/)
📊 Feature Status Matrix
| Feature | Status | Config Required | HIPAA-Safe | Notes |
|---|---|---|---|---|
| Audio Backups | ✅ Working | None (auto) | ✅ Yes | Server + IndexedDB |
| S3 Documents | ✅ Working | S3_BUCKET | ✅ Yes (AWS) | Optional feature |
| Browser Whisper | ✅ Working | None (optional) | ✅ Yes | Client-side only |
| Voice Preferences | ✅ Working | Provider config | Depends | Google/AWS = yes |
| Learning Hub Path | ✅ Working | Nextcloud config | ✅ Yes | User preference |
| TTS Preview | ✅ Fixed | TTS provider | Depends | Check logs if fails |
| Embeddings | ✅ Working | Vertex/LiteLLM | ✅ Yes | Requires pgvector |
🚀 Next Steps
- Push v14 to Docker (in progress via GitHub Actions)
- Test features after deployment
- Check browser console for any errors
- Verify TTS preview works with your provider
- Test browser whisper download with different models
Questions? Check the logs:
- Browser: F12 → Console tab
- Server:
docker logs pediatric-ai-scribe -f - Database:
psql -d pedscribe -c "SELECT COUNT(*) FROM audio_backups;"