Covers: architecture, directory structure, all env vars, full DB schema,
auth system, every API endpoint with auth level, frontend tab loading
pattern (critical: tabChanged not DOMContentLoaded), AI integration,
Learning Hub content types, deployment commands, security notes,
admin password reset via Docker console, and how to add new features.
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.
Delete bar bug:
- Root cause: .lh-delete-confirm-bar{display:flex} defined after .hidden{display:none}
so flex overrode hidden (no !important on global .hidden rule)
- Fix: remove display:flex from base rule, use .lh-delete-confirm-bar:not(.hidden){display:flex}
so hidden class always wins regardless of CSS order
Slide preview (replace window.open + document.write approach):
- Backend /preview-slides now returns JSON: {css, slides[]} (individual <section> HTML)
- In-page full-screen modal with dark overlay, white slide frame
- Prev/Next arrow buttons, dot indicators, slide counter
- Keyboard navigation (arrow keys, Escape to close)
- Touch/swipe support for mobile
- Overlay click closes modal
- No popups required, works in all browsers
Marp textarea:
- Placeholder updated to guide users: "Click AI Generate above to create slides"
- Makes it clear AI is the primary authoring path; textarea for review/edits
Delete confirmation:
- Replaced browser confirm() popup with inline red warning bar in editor
- Shows content title, Cancel and Delete buttons inline in the editor header
- Category delete uses double-click pattern with toast feedback
Login screen:
- Changed from dark blue/purple gradient to light gray background (var(--g50))
- Card now matches 404 page style: white, subtle blue shadow, indigo border
Presentation content type:
- New 'Presentation' button in CMS toolbar (green style)
- Marp markdown textarea editor (dark code editor style) shown when type=presentation
- Body/Tiptap editor hidden; Marp section shown (toggleEditorMode)
- Preview button: renders Marp HTML via @marp-team/marp-core, opens in new tab
- Download PPTX: pure-JS pptxgenjs (no Chromium), parses slides, exports .pptx
- AI generate: returns Marp markdown directly when contentType=presentation
- Body column stores Marp markdown for presentations
Backend:
- POST /api/admin/learning/generate-pptx — pptxgenjs PPTX from Marp markdown
- POST /api/admin/learning/preview-slides — Marp HTML preview
GitHub repo set to private via API.
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 /).
- Bundle Tiptap 2 (StarterKit, Link, Underline) with esbuild into
public/vendor/tiptap.bundle.js — fully self-hosted, no CDN needed
- Remove Quill (quill.min.js, quill.snow.css, patchQuillTooltip hack)
- Headless Tiptap editor: custom toolbar (bold/italic/underline/strike,
headings, lists, blockquote, code, link) with active-state highlighting
- Link insertion: inline link bar below toolbar (no popup, no tooltip)
— type URL, press Apply or Enter; Remove button to unset
- Mini editor for question text and option text (same toggle UX)
- ProseMirror content styles: headings, lists, blockquote, code, links
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.
- wellVisit.js now handles tabChanged for vaxschedule and catchup tabs
so renderFullSchedule() and renderCatchUp() fire when those tabs open
- Replace Quill CDN with local /vendor/quill.min.js + quill.snow.css
(CDN was unreachable in local/offline environments)
- Replace textarea+toolbar with Quill.js (free, MIT) for body editor
- Full WYSIWYG: headings, bold/italic, lists, blockquote, code, link (inline, no popup)
- Rich Text toggle button on each question text field and option text field
- Plain textarea by default; toggle activates mini Quill editor per field
- New 'Multiple Select' question type: checkboxes, all correct must be chosen
- Backend scoring: allCorrectChosen && noWrongChosen
- Results show all correct options highlighted, wrong selections flagged
- quiz display uses sanitizeHtml for question/option text (supports formatted HTML)
- sanitizeHtml allows Quill class attrs (ql-indent, etc.)
All JS modules were running immediately (IIFE/DOMContentLoaded) but
tab HTML is lazy-loaded from /components/*.html, causing null errors
on every addEventListener call. Each module now listens for tabChanged
with its tab name and initializes once after the DOM is ready.
Also: quiz question/explanation boxes changed to textarea for
multi-line support; quiz display uses sanitizeHtml() so formatted
text (bold, lists, etc.) renders correctly.
Adds setOutputText() helper that escapes HTML and converts \n to <br>,
applied to all AI-generated note outputs so line breaks are preserved
in contenteditable divs. Also cleans up CSS for quiz option markers.
- 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
- WNL/Normal systems now expand to 1-3 specific pertinent negatives instead of
just "WNL" — varies each time, relates to chief complaint and differential
(e.g., Skin: "no rash, no petechiae, no bruising" instead of "WNL")
- Abnormal systems: AI expands physician's brief note into clinical description
favoring most common presentation, adds only 1-2 related pertinent negatives
(e.g., "pimples on face" → "inflammatory papules on bilateral cheeks...")
- Format function now passes system domain details for context
- Rules applied to well visit (full + short) and sick visit prompts
- Growth reference data by age (newborn through 21y): expected weight/length/HC
gains per day/month/year, puberty growth spurt details
- Feeding/nutrition guidance by age: formula amounts, breastfeeding frequency,
solid food introduction timeline, juice limits, milk transitions, calorie needs
- BMI/obesity classification per AAP 2023 CPG + CDC Extended BMI:
Underweight, Healthy, Overweight, Obesity Class I/II/III with action items
(≥120% and ≥140% of 95th percentile thresholds)
- Weight-for-length guidance for children <2 years
- "By Visit" panel now shows growth reference + feeding guidance + BMI table
- Well visit AI prompt updated to include growth assessment, BMI classification,
and feeding counseling in generated notes
- Backend injects age-appropriate growth/nutrition data into AI context
- Bedrock: Add 14 non-vendor model models (Amazon Nova, Meta Llama 4, DeepSeek R1/V3,
Mistral Large 3, Cohere Command R+, AI21 Jamba) with verified AWS model IDs
- Bedrock: Implement Converse API for non-Anthropic models (unified cross-model API),
keep native Messages API for vendor model models (best performance)
- Milestones: Add Newborn / 1 month developmental milestones (Gross Motor, Fine Motor,
Language, Social/Emotional, Cognitive) — previously started at 2 months
- Bump version to 3.0.0, docker-compose tag to v3.0
- Source: docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html
- Added: vendor model Opus 4.6 (anthropic.agent-config-opus-4-6-v1)
- Added: vendor model Opus 4.5 (anthropic.agent-config-opus-4-5-20251101-v1:0)
- Added: vendor model Sonnet 4.6 (anthropic.agent-config-sonnet-4-6) — new default
- Added: vendor model Sonnet 4.5 (anthropic.agent-config-sonnet-4-5-20250929-v1:0)
- Kept: vendor model Sonnet 4, Haiku 4.5, vendor model 3.5 Haiku
- Fallback updated to vendor model Haiku 4.5
- Bump version to 2.9.0
- resetChartReview() clears all pasted notes, visit cards, labs, demographics, and output on New
- clearTab('chart') in encounters.js now calls resetChartReview() for complete reset
- chartReview route injects today's date so AI understands time-relative terms
- Bedrock models updated to vendor model-only with verified 2025 IDs (Sonnet 4, 3.7 Sonnet, 3.5 Sonnet v2, 3.5 Haiku)
- Removed Llama/Mistral/Titan from Bedrock; fixed incorrect vendor-model-4.6 Bedrock IDs
- Bump version to 2.8.0
- Copy to Note button now switches to Well Visit → Visit Note subtab
automatically so user sees the result immediately
- Add complete AAP/Nelson developmental milestones for ages 6, 7, 8, 9,
10, and 11 years (Gross Motor, Fine Motor, Language, Social/Emotional,
Cognitive domains each)
- Add corresponding age options to ms-age-group select
- 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
- CSS: .enc-load-popover.hidden now overrides display:flex (specificity bug
caused popovers to always be visible)
- resumeEncounter: add domPfxMap to correctly resolve label element IDs
(enc_type 'wellvisit' → 'wv-label', 'hospital' → 'hosp-label', etc.)
- resumeEncounter: use noteIdMap to find the right output element per tab
- resumeEncounter: use enc.enc_type (not tabName) as _savedEncId_ key for
consistency with saveFromTab
- 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
- 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)