Ask a real clinical question. Citations stay linked to the source cards on the right.
+
Pediatric Clinical Assistant
+
Answers built only from your indexed library
diff --git a/public/css/assistant.css b/public/css/assistant.css
index 70445771..91c49b2b 100644
--- a/public/css/assistant.css
+++ b/public/css/assistant.css
@@ -134,6 +134,27 @@ body.assistant-workspace .assistant-topbar-title h2 { position:absolute; width:1
.assistant-sources { padding:10px 12px; display:grid; gap:8px; flex:1 1 auto; min-height:0; overflow-y:auto; }
.assistant-saved-chats { flex:1 1 auto; overflow-y:auto; min-height:0; padding:10px 12px; display:flex; flex-direction:column; gap:8px; }
/* Open WebUI-style recency headings above each run of chats. */
+/* Composed empty state: a title, one status pill and the composer — nothing
+ else competing for attention before the first question is asked. */
+.assistant-empty h3 { color:var(--g900); font-size:26px; font-weight:600; letter-spacing:-.01em; margin:0 0 12px; }
+.assistant-empty-badge { display:inline-flex; align-items:center; gap:7px; margin:0; padding:7px 14px; border:1px solid var(--g200); border-radius:999px; background:white; box-shadow:var(--shadow); font-size:12.5px; color:var(--g600); }
+.assistant-empty-badge i { color:var(--purple); font-size:12px; }
+.assistant-examples button { border:1px solid var(--g200); background:white; color:var(--g700); border-radius:10px; padding:9px 14px; font-size:12.5px; box-shadow:var(--shadow); }
+.assistant-examples button:hover { border-color:var(--purple-light); background:#faf5ff; color:var(--purple); }
+
+/* A faint graph-paper ground so the composer card reads as a raised surface. */
+.assistant-messages { background-image:linear-gradient(var(--g100) 1px, transparent 1px), linear-gradient(90deg, var(--g100) 1px, transparent 1px); background-size:28px 28px; background-position:-1px -1px; }
+.assistant-messages:has(.assistant-msg) { background-image:none; }
+
+/* New chat carries its shortcut, the way a command row does. */
+.assistant-new-chat { justify-content:flex-start; }
+.assistant-kbd { margin-left:auto; font:inherit; font-size:10px; color:var(--g400); background:var(--g100); border:1px solid var(--g200); border-radius:5px; padding:2px 5px; white-space:nowrap; }
+
+/* Saved Chats collapses, like the Chats group in the reference UI. */
+.assistant-chats-header { display:flex; align-items:center; justify-content:space-between; width:100%; border:0; background:none; cursor:pointer; text-align:left; }
+.assistant-chats-caret { font-size:10px; color:var(--g400); transition:transform .15s ease; }
+.assistant-chats-header[aria-expanded="false"] .assistant-chats-caret { transform:rotate(-90deg); }
+
/* Two-mode switch at the top of the rail, the way Home and Code swap the page. */
.assistant-mode-switch { display:grid; grid-template-columns:1fr 1fr; gap:4px; background:var(--g100); border:1px solid var(--g200); border-radius:10px; padding:4px; margin-bottom:8px; }
.assistant-mode-switch button { display:flex; align-items:center; justify-content:center; gap:6px; border:0; border-radius:7px; padding:7px 6px; font-size:12px; font-weight:600; color:var(--g500); background:transparent; cursor:pointer; }
diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js
index 2432c64b..fc7d1b6a 100644
--- a/public/js/clinicalAssistant.js
+++ b/public/js/clinicalAssistant.js
@@ -234,6 +234,28 @@ import {
if (typeof window.activateTab === 'function') window.activateTab(link.getAttribute('data-assistant-workspace-tab'));
});
+ var chatsToggle = document.getElementById('btn-assistant-chats-toggle');
+ if (chatsToggle) chatsToggle.addEventListener('click', function() {
+ var list = document.getElementById('assistant-saved-chats');
+ if (!list) return;
+ var open = list.hidden;
+ list.hidden = !open;
+ chatsToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
+ try { localStorage.setItem('ped_assistant_chats_open', open ? '1' : '0'); } catch (e) {}
+ });
+ try {
+ if (localStorage.getItem('ped_assistant_chats_open') === '0' && chatsToggle) chatsToggle.click();
+ } catch (e) {}
+
+ // Ctrl+Shift+O starts a new chat, as advertised next to the button.
+ document.addEventListener('keydown', function(event) {
+ if (!event.ctrlKey || !event.shiftKey || String(event.key).toLowerCase() !== 'o') return;
+ if (!document.body.classList.contains('assistant-workspace')) return;
+ event.preventDefault();
+ var button = document.getElementById('btn-assistant-clear');
+ if (button) button.click();
+ });
+
var closeDrawer = function() {
var layout = document.getElementById('assistant-layout');
if (layout) layout.classList.remove('mobile-chats-open');
diff --git a/test/assistant-component-css.test.js b/test/assistant-component-css.test.js
index a2b3f60d..0b85c577 100644
--- a/test/assistant-component-css.test.js
+++ b/test/assistant-component-css.test.js
@@ -150,3 +150,31 @@ test('out-of-order CSS completion does not initialize an inactive tab; returning
assert.equal(tab.querySelector('[role="status"]'), null);
dom.window.close();
});
+
+test('the empty state is composed, not a stack of competing blocks', () => {
+ const fs = require('node:fs');
+ const path = require('node:path');
+ const root = path.join(__dirname, '..');
+ const html = fs.readFileSync(path.join(root, 'public/components/assistant.html'), 'utf8');
+ assert.match(html, /assistant-empty-badge/, 'one status pill under the title');
+ assert.doesNotMatch(html, /<\/i>\s*
/, 'no oversized icon above the title');
+
+ const css = fs.readFileSync(path.join(root, 'public/css/assistant.css'), 'utf8');
+ // The graph-paper ground reads as a surface only while the transcript is
+ // empty; once there are messages it would fight the text.
+ assert.match(css, /\.assistant-messages:has\(\.assistant-msg\) \{ background-image:none; \}/);
+ assert.match(css, /\.assistant-kbd/, 'New chat advertises its shortcut');
+ assert.match(css, /\.assistant-chats-header\[aria-expanded="false"\] \.assistant-chats-caret/, 'the chats group collapses');
+});
+
+test('the advertised New chat shortcut actually works, and only inside the assistant', () => {
+ const fs = require('node:fs');
+ const path = require('node:path');
+ const js = fs.readFileSync(path.join(__dirname, '..', 'public/js/clinicalAssistant.js'), 'utf8');
+ const handler = js.slice(js.indexOf('// Ctrl+Shift+O starts a new chat'), js.indexOf('var closeDrawer'));
+ assert.match(handler, /event\.ctrlKey && event\.shiftKey|!event\.ctrlKey \|\| !event\.shiftKey/);
+ assert.match(handler, /toLowerCase\(\) !== 'o'/);
+ // Must not hijack Ctrl+Shift+O for the rest of the app.
+ assert.match(handler, /classList\.contains\('assistant-workspace'\)/);
+ assert.match(handler, /btn-assistant-clear/);
+});