Symptom Daniel reported: "note recording, not working nor going into
textbox". Root cause was on the server side — /api/notes/from-voice
asked the AI for HTML in its prompt, but real-world models return
markdown ~25% of the time. Tiptap's setContent only renders HTML;
markdown comes through as literal text or a partial render, looking
like the textbox didn't fill.
Server (src/routes/notes.js):
• New toHtmlBody() helper. If the AI returned real HTML (any
block tag), pass through. Otherwise run through `marked` so
markdown becomes <p>/<h*>/<strong>/etc.
• Strips ```json / ```html code fences before JSON parsing.
• Stricter JSON-recovery: only accepts {title|body} parsed shape;
falls back to wrapping the AI's full reply via toHtmlBody().
• Final guard: if body would be empty after sanitisation, wrap
the raw transcript so the user can at least edit it manually.
Client (public/js/notes.js):
• applyGeneratedNote prefers _editor.commands.setContent over a
full remount — avoids the toolbar-reattach flicker + the brief
window where the body looked empty.
• Logs to console when the editor target is missing or Tiptap
setContent throws, so a future regression is greppable.
Plus the two infra fixes Daniel approved earlier in the same
session — keeping them in this commit since they're already
deployed and tested:
• src/db/database.js: cleanup interval handle exposed; server
shutdown now clearInterval()s it before pool.end(). Removes
the SIGTERM → 9-second-hang → Docker SIGKILL race.
• src/routes/audioBackups.js: switch multer.memoryStorage() to
diskStorage with cleanup. 10 concurrent 25 MB uploads no
longer pin 250 MB of RAM. Identical user-visible perf since
upload is wire-bound.
New dep: marked@latest (used server-side only in toHtmlBody).
93 lines
2.8 KiB
JavaScript
93 lines
2.8 KiB
JavaScript
// ============================================================
|
|
// SERVICE WORKER — Cache shell, network-first for API
|
|
// Provides offline fallback for app shell while keeping
|
|
// API calls always fresh (critical for medical data accuracy)
|
|
// ============================================================
|
|
|
|
var CACHE_NAME = 'pedscribe-v12-notes6';
|
|
var SHELL_ASSETS = [
|
|
'/',
|
|
'/index.html',
|
|
'/css/styles.css',
|
|
'/js/app.js',
|
|
'/js/auth.js',
|
|
'/manifest.json'
|
|
];
|
|
|
|
// Install: precache app shell
|
|
self.addEventListener('install', function(event) {
|
|
event.waitUntil(
|
|
caches.open(CACHE_NAME).then(function(cache) {
|
|
return cache.addAll(SHELL_ASSETS);
|
|
}).then(function() {
|
|
return self.skipWaiting();
|
|
})
|
|
);
|
|
});
|
|
|
|
// Activate: clear old caches
|
|
self.addEventListener('activate', function(event) {
|
|
event.waitUntil(
|
|
caches.keys().then(function(names) {
|
|
return Promise.all(
|
|
names.filter(function(name) { return name !== CACHE_NAME; })
|
|
.map(function(name) { return caches.delete(name); })
|
|
);
|
|
}).then(function() {
|
|
return self.clients.claim();
|
|
})
|
|
);
|
|
});
|
|
|
|
// Fetch: network-first for API, cache-first for static assets
|
|
self.addEventListener('fetch', function(event) {
|
|
var url = new URL(event.request.url);
|
|
|
|
// Only handle same-origin requests
|
|
if (url.origin !== self.location.origin) return;
|
|
|
|
// API calls — always network, never cache (medical data must be fresh)
|
|
if (url.pathname.startsWith('/api/')) {
|
|
event.respondWith(fetch(event.request));
|
|
return;
|
|
}
|
|
|
|
// Component HTML — network-first with cache fallback
|
|
if (url.pathname.startsWith('/components/')) {
|
|
event.respondWith(
|
|
fetch(event.request).then(function(response) {
|
|
var clone = response.clone();
|
|
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
|
|
return response;
|
|
}).catch(function() {
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Static assets (JS, CSS, icons) — network-first so code updates apply immediately
|
|
if (url.pathname.match(/\.(js|css|png|ico|woff2?)$/)) {
|
|
event.respondWith(
|
|
fetch(event.request).then(function(response) {
|
|
var clone = response.clone();
|
|
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
|
|
return response;
|
|
}).catch(function() {
|
|
return caches.match(event.request);
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// HTML pages — network-first
|
|
event.respondWith(
|
|
fetch(event.request).then(function(response) {
|
|
var clone = response.clone();
|
|
caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, clone); });
|
|
return response;
|
|
}).catch(function() {
|
|
return caches.match(event.request) || caches.match('/index.html');
|
|
})
|
|
);
|
|
});
|