diff --git a/public/css/styles.css b/public/css/styles.css index 2b38f8c..c419f3c 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1139,6 +1139,8 @@ textarea.full-input{resize:vertical;} .docs-reader-body th{background:var(--g50);font-weight:600;} .docs-reader-body a{color:var(--blue);text-decoration:none;} .docs-reader-body a:hover{text-decoration:underline;} +.docs-reader-body .docs-anchor-link{color:var(--blue);cursor:pointer;text-decoration:none;} +.docs-reader-body .docs-anchor-link:hover{text-decoration:underline;} .docs-reader-body hr{border:0;border-top:1px solid var(--g200);margin:1.6em 0;} @media (max-width:900px){ diff --git a/public/js/admin-docs.js b/public/js/admin-docs.js index 693301b..d1ffc07 100644 --- a/public/js/admin-docs.js +++ b/public/js/admin-docs.js @@ -132,6 +132,26 @@ }); } + function prepareDocLinks(body) { + if (!body) return; + body.querySelectorAll('a[href]').forEach(function (link) { + var href = link.getAttribute('href') || ''; + if (href.charAt(0) !== '#' && link.hash) { + try { + var u = new URL(link.href, window.location.href); + if (u.origin !== window.location.origin || u.pathname !== window.location.pathname) return; + href = u.hash; + } catch (_) { return; } + } + if (!href || href.charAt(0) !== '#') return; + link.dataset.docHash = href; + link.removeAttribute('href'); + link.setAttribute('role', 'button'); + link.setAttribute('tabindex', '0'); + link.classList.add('docs-anchor-link'); + }); + } + function scrollReaderToHash(hash) { var body = $('docs-reader-body'); var reader = $('docs-reader'); @@ -152,25 +172,24 @@ } function handleDocAnchorClick(e) { - var link = e.target.closest('a[href]'); + var link = e.target.closest('[data-doc-hash]'); var body = $('docs-reader-body'); if (!link || !body || !body.contains(link)) return; - var href = link.getAttribute('href') || ''; - if (href.charAt(0) !== '#' && link.hash) { - try { - var u = new URL(link.href, window.location.href); - if (u.origin !== window.location.origin || u.pathname !== window.location.pathname) return; - href = u.hash; - } catch (_) { return; } - } + var href = link.dataset.docHash || ''; if (!href || href.charAt(0) !== '#') return; e.preventDefault(); requestAnimationFrame(function () { - scrollReaderToHash(href); - try { history.replaceState(null, '', href); } catch (_) { window.location.hash = href; } + if (scrollReaderToHash(href)) { + try { history.replaceState(null, '', href); } catch (_) {} + } }); } + function handleDocAnchorKeydown(e) { + if (e.key !== 'Enter' && e.key !== ' ') return; + handleDocAnchorClick(e); + } + function loadTree() { if (_treeLoaded) return; fetch('/api/admin/docs/tree', { headers: getAuthHeaders() }) @@ -217,6 +236,7 @@ } body.innerHTML = data.html || ''; prepareDocAnchors(body); + prepareDocLinks(body); if (meta) { meta.textContent = relPath + ' • ' + (data.bytes != null ? (data.bytes + ' bytes') : ''); } @@ -277,6 +297,7 @@ if (body && !body.dataset.anchorsWired) { body.dataset.anchorsWired = '1'; body.addEventListener('click', handleDocAnchorClick); + body.addEventListener('keydown', handleDocAnchorKeydown); } var filter = $('docs-filter'); diff --git a/public/js/e2e-bootstrap.js b/public/js/e2e-bootstrap.js index c3cfda1..346b038 100644 --- a/public/js/e2e-bootstrap.js +++ b/public/js/e2e-bootstrap.js @@ -1,7 +1,7 @@ // E2E harness bootstrap — external file because the app's CSP blocks inline scripts. // Runs after calc-math.js + calculators.js (all three have `defer`, so execution // is in document order once parsing completes). -(async function bootstrap() { +async function bootstrapE2eHarness() { try { // Cache-bust so stale playwright browser caches never serve an outdated // component HTML between reorg deploys. @@ -20,4 +20,5 @@ console.error('[e2e-harness] bootstrap failed:', e); window.__harnessError = String(e); } -})(); +} +bootstrapE2eHarness(); diff --git a/public/js/encounters.js b/public/js/encounters.js index 5ae8844..19ba7ca 100644 --- a/public/js/encounters.js +++ b/public/js/encounters.js @@ -2,7 +2,7 @@ // ENCOUNTERS.JS — Save/resume/pause encounter progress // ============================================================ -(function() { +function setupEncountersModule() { // ── Pause/Resume for recording buttons ───────────────────────────────── // Each recording module (encounter, dictation) gets a pause button wired here. @@ -94,14 +94,15 @@ } // Restore saved encounter IDs from sessionStorage (survive page refresh, cleared on tab close) - (function() { + function restoreSavedEncounterIds() { ['encounter','dictation','ed','hospital','chart','wellvisit','sickvisit','soap'].forEach(function(t) { try { var id = sessionStorage.getItem('_savedEncId_' + t); if (id) window['_savedEncId_' + t] = id; } catch(e) {} }); - })(); + } + restoreSavedEncounterIds(); // Register a load handler for a specific tab type window.registerEncounterLoadHandler = function(type, fn) { @@ -513,4 +514,5 @@ console.log('✅ Encounters module loaded'); -})(); +} +setupEncountersModule(); diff --git a/public/js/liveEncounter.js b/public/js/liveEncounter.js index 972141f..f1650bb 100644 --- a/public/js/liveEncounter.js +++ b/public/js/liveEncounter.js @@ -1,8 +1,7 @@ -(function() { - var _inited = false; +var _liveEncounterInited = false; document.addEventListener('tabChanged', function(e) { - if (e.detail.tab !== 'encounter' || _inited) return; - _inited = true; + if (e.detail.tab !== 'encounter' || _liveEncounterInited) return; + _liveEncounterInited = true; var recordBtn = document.getElementById('enc-record-btn'); var pauseBtn = document.getElementById('enc-pause-btn'); var indicator = document.getElementById('enc-recording-indicator'); @@ -239,4 +238,3 @@ console.log('✅ Encounter module loaded'); }); -})(); diff --git a/public/js/shadess.js b/public/js/shadess.js index 7799f3d..8ad5c57 100644 --- a/public/js/shadess.js +++ b/public/js/shadess.js @@ -2,7 +2,7 @@ // SHADESS.JS — SSHADESS psychosocial screening form + well visit note // ============================================================ -(function() { +function setupShadessModule() { // ── SSHADESS domain definitions ────────────────────────────────────────── // Each domain has key questions (most important shown first) + comment field @@ -1059,4 +1059,5 @@ console.log('SHADESS module loaded'); -})(); +} +setupShadessModule(); diff --git a/public/js/voiceDictation.js b/public/js/voiceDictation.js index 63286eb..676e291 100644 --- a/public/js/voiceDictation.js +++ b/public/js/voiceDictation.js @@ -1,8 +1,7 @@ -(function() { - var _inited = false; +var _voiceDictationInited = false; document.addEventListener('tabChanged', function(e) { - if (e.detail.tab !== 'dictation' || _inited) return; - _inited = true; + if (e.detail.tab !== 'dictation' || _voiceDictationInited) return; + _voiceDictationInited = true; var recordBtn = document.getElementById('dict-record-btn'); var pauseBtn = document.getElementById('dict-pause-btn'); var indicator = document.getElementById('dict-recording-indicator'); @@ -210,4 +209,3 @@ console.log('✅ Dictation module loaded'); }); -})(); diff --git a/src/db/database.js b/src/db/database.js index fab327c..cf56f52 100644 --- a/src/db/database.js +++ b/src/db/database.js @@ -436,7 +436,7 @@ async function initDatabase() { // Boot sequence: inline init (idempotent baseline) → node-pg-migrate for // incremental changes. Migrations live in /app/migrations/. -(async function() { +async function bootstrapDatabase() { await initDatabase(); try { var { runMigrations } = require('./migrate'); @@ -447,7 +447,8 @@ async function initDatabase() { // broken DB separately. console.error('[DB] Migration runner failed:', e.message); } -})(); +} +bootstrapDatabase(); // Clean up expired saved encounters and audio backups async function cleanupExpired() { diff --git a/src/routes/auth.js b/src/routes/auth.js index e8a185b..469951e 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -568,7 +568,7 @@ router.post('/forgot-password', async (req, res) => { // Fire-and-forget: DB lookup + token + email happen after the response // is sent, so their latency cannot be measured by the caller. - (async function () { + async function sendResetEmailInBackground() { try { var user = await db.get('SELECT id FROM users WHERE email = ?', [email]); if (!user) return; @@ -586,7 +586,8 @@ router.post('/forgot-password', async (req, res) => { } catch (bgErr) { console.error('[Auth] forgot-password background send failed:', bgErr.message); } - })(); + } + sendResetEmailInBackground(); } catch (err) { console.error('[Auth] Forgot password error:', err.message); res.status(500).json({ error: 'Password reset request failed' }); } }); diff --git a/src/routes/learningAdmin.js b/src/routes/learningAdmin.js index cad7b99..fae2a47 100644 --- a/src/routes/learningAdmin.js +++ b/src/routes/learningAdmin.js @@ -350,7 +350,7 @@ router.post('/embeddings/generate', async function(req, res) { console.log('[Embeddings] Starting batch generation for ' + content.length + ' items...'); // Don't await — run in background - (async function() { + async function generateEmbeddingsInBackground() { for (var i = 0; i < content.length; i++) { var item = content[i]; try { @@ -384,7 +384,8 @@ router.post('/embeddings/generate', async function(req, res) { console.error('[Embeddings] Index creation failed:', e.message); } } - })(); + } + generateEmbeddingsInBackground(); res.json({ success: true,