fix: remove the assistant's second top bar and give the toggle a menu icon
The assistant carried its own bar above the layout, on top of the blue app header that both pages now share. On desktop it held a visually-hidden title, a mobile-only drawer button and a status pill — so it was mostly dead space, and it made the assistant taller than the app page it is supposed to match. The bar is gone. The ready/busy indicator moved into the composer footer, next to the work it describes, and the drawer button floats on phones rather than occupying a strip the desktop layout does not have. The rail now sticks to the top of the layout instead of 52px below a bar that no longer exists. The menu toggle used fa-table-columns, a split-pane glyph that does not read as "menu". Both copies now use fa-bars and rotate when collapsed to show which way they will act. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmpYHPSLGmXGZMyLpn2Lbe
This commit is contained in:
parent
e1e23a99e6
commit
b55a748361
4 changed files with 46 additions and 35 deletions
|
|
@ -1,20 +1,10 @@
|
|||
<div class="assistant-topbar">
|
||||
<button id="btn-assistant-mobile-menu" class="btn-sm btn-ghost assistant-mobile-menu" type="button" title="Saved chats"><i class="fas fa-bars"></i></button>
|
||||
<div class="assistant-topbar-title">
|
||||
<h2><i class="fas fa-brain" style="color:var(--purple);"></i> AI Clinical Assistant</h2>
|
||||
</div>
|
||||
<div class="assistant-status" id="assistant-status">
|
||||
<span class="assistant-dot"></span>
|
||||
<span id="assistant-status-text">Ready</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assistant-layout" id="assistant-layout">
|
||||
<button id="btn-assistant-mobile-menu" class="assistant-mobile-menu" type="button" title="Saved chats" aria-label="Saved chats"><i class="fas fa-bars"></i></button>
|
||||
<div class="assistant-drawer-backdrop" id="assistant-drawer-backdrop"></div>
|
||||
<aside class="assistant-history">
|
||||
<button id="btn-assistant-drawer-close" class="assistant-drawer-close" type="button" aria-label="Close saved chats"><i class="fas fa-xmark"></i></button>
|
||||
<p class="assistant-preview-note"><i class="fas fa-circle-info"></i> You are previewing. Sign in to save chats, export and generate images.</p>
|
||||
<button type="button" class="assistant-menu-toggle" data-menu-toggle aria-expanded="true" title="Hide menu" aria-label="Hide menu"><i class="fas fa-table-columns"></i></button>
|
||||
<button type="button" class="assistant-menu-toggle" data-menu-toggle aria-expanded="true" title="Hide menu" aria-label="Hide menu"><i class="fas fa-bars"></i></button>
|
||||
<div class="assistant-mode-switch" role="tablist" aria-label="Assistant or workspace">
|
||||
<button type="button" role="tab" class="active" aria-selected="true" data-assistant-mode="assistant"><i class="fas fa-brain"></i> Assistant</button>
|
||||
<button type="button" role="tab" aria-selected="false" data-assistant-mode="workspace"><i class="fas fa-grip"></i> Workspace</button>
|
||||
|
|
@ -79,6 +69,7 @@
|
|||
</div>
|
||||
<input type="file" id="assistant-attach-input" accept="image/png,image/jpeg,image/webp" multiple hidden>
|
||||
<span id="assistant-autosave-state" class="assistant-autosave-state" aria-live="polite"></span>
|
||||
<span class="assistant-status" id="assistant-status"><span class="assistant-dot"></span><span id="assistant-status-text">Ready</span></span>
|
||||
</div>
|
||||
<div class="assistant-composer-right">
|
||||
<div class="assistant-model-pill" id="assistant-model-pill" hidden>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
|
||||
.assistant-header { display:flex; justify-content:space-between; gap:12px; align-items:flex-start; }
|
||||
.assistant-mobile-menu { display:none; }
|
||||
.assistant-topbar { display:flex; align-items:center; gap:12px; padding:6px 12px; border-bottom:1px solid var(--g200); background:var(--g50); position:sticky; top:0; z-index:4; }
|
||||
.assistant-topbar-title { flex:1; min-width:0; position:relative; }
|
||||
.assistant-topbar-title h2 { font-size:15px; margin:0; color:var(--g800); }
|
||||
/* Inside the assistant workspace the page IS the assistant; naming it again in
|
||||
the topbar just costs a row of space. Kept in the DOM for screen readers. */
|
||||
body.assistant-workspace .assistant-topbar-title h2 { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
|
||||
.assistant-topbar-title h2 i { margin-right:6px; }
|
||||
.assistant-status { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--g500); background:white; border:1px solid var(--g200); border-radius:999px; padding:5px 10px; box-shadow:var(--shadow); }
|
||||
.assistant-status { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--g400); }
|
||||
.assistant-dot { width:8px; height:8px; border-radius:50%; background:var(--green); display:inline-block; }
|
||||
.assistant-status.busy .assistant-dot { background:var(--amber); animation:pulse 1.5s infinite; }
|
||||
.assistant-status.error .assistant-dot { background:var(--red); }
|
||||
.assistant-layout { display:grid; grid-template-columns:260px minmax(0,1fr) 330px; gap:14px; align-items:stretch; }
|
||||
.assistant-layout > * { min-width:0; }
|
||||
.assistant-history { display:grid; gap:10px; align-content:start; position:sticky; top:52px; }
|
||||
.assistant-history { display:grid; gap:10px; align-content:start; position:sticky; top:0; }
|
||||
.assistant-new-chat { display:flex; align-items:center; justify-content:center; gap:8px; width:100%; border:1px solid var(--blue-light); background:var(--blue-light); color:var(--blue); border-radius:10px; padding:10px 12px; font-size:13px; font-weight:700; cursor:pointer; }
|
||||
.assistant-new-chat:hover { border-color:var(--blue); background:var(--blue-light); }
|
||||
.assistant-main { display:flex; flex-direction:column; min-height:calc(100vh - 200px); min-width:0; }
|
||||
|
|
@ -192,12 +187,9 @@ body.assistant-workspace .assistant-topbar-title h2 { position:absolute; width:1
|
|||
@media (max-width: 960px) { .assistant-layout { grid-template-columns:minmax(0,1fr) 300px; } .assistant-history { position:static; grid-column:1 / -1; grid-template-columns:auto 1fr; align-items:start; } .assistant-new-chat { grid-column:1; } .assistant-view-switch { grid-column:1; } .assistant-history .card { grid-column:2; grid-row:1 / span 2; } .assistant-main { min-height:auto; } #assistant-chat-view { min-height:auto; grid-template-rows:minmax(0,1fr) auto; } }
|
||||
@media (max-width: 640px) {
|
||||
/* ── ChatGPT/OWUI mobile: independent full-screen overlay; the page behind never scrolls ── */
|
||||
.assistant-topbar { flex-wrap:nowrap; gap:8px; padding:10px 14px; }
|
||||
.assistant-mobile-menu { display:inline-flex; }
|
||||
.assistant-topbar .btn-sm { padding:6px 8px; }
|
||||
#btn-assistant-goback span, #btn-assistant-goback { font-size:0; }
|
||||
#btn-assistant-goback { padding:8px; }
|
||||
.assistant-topbar-title h2 { font-size:15px; }
|
||||
.assistant-status { margin-left:auto; font-size:11px; }
|
||||
/* The assistant fills the viewport exactly; nothing behind can scroll */
|
||||
.assistant-layout { display:flex; flex-direction:column; height:100dvh; min-height:0; overflow:hidden; }
|
||||
|
|
@ -417,7 +409,6 @@ body.assistant-workspace #assistant-chat-view { min-height: 0; }
|
|||
.assistant-goback-rail, .assistant-create-image, .assistant-new-chat { font-size:13px; padding:11px 12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.assistant-history .card-header h3 { font-size:11px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
/* The topbar keeps only the hamburger; Go back is in the menu */
|
||||
.assistant-topbar #btn-assistant-goback { display:none; }
|
||||
}
|
||||
/* Library item hover download */
|
||||
.assistant-gallery-item-wrap { position:relative; }
|
||||
|
|
@ -503,3 +494,15 @@ body.assistant-mode-workspace .assistant-rail-workspace { display:flex; }
|
|||
#assistant-chat-view:has(.assistant-messages:not(:has(.assistant-msg))) .assistant-composer {
|
||||
margin:0 10px 10px; width:auto; max-width:none; }
|
||||
}
|
||||
|
||||
/* The drawer button floats on phones instead of occupying a bar the desktop
|
||||
layout does not have — that bar was the dead strip at the top and the reason
|
||||
the assistant and the workspace were different heights. */
|
||||
.assistant-mobile-menu { display:none; }
|
||||
@media (max-width:640px) {
|
||||
.assistant-mobile-menu { display:inline-flex; align-items:center; justify-content:center; position:absolute; top:8px; left:8px; z-index:70; width:36px; height:36px; border:1px solid var(--g200); border-radius:10px; background:white; color:var(--g600); font-size:14px; box-shadow:var(--shadow); }
|
||||
.assistant-layout { position:relative; }
|
||||
}
|
||||
/* Both toggles point the way they act, and flip when collapsed. */
|
||||
.assistant-menu-toggle i { transition:transform .15s ease; }
|
||||
.assistant-menu-toggle.is-collapsed i { transform:rotate(180deg); }
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="sidebar-nav">
|
||||
<button type="button" class="assistant-menu-toggle" data-menu-toggle aria-expanded="true" title="Hide menu" aria-label="Hide menu"><i class="fas fa-table-columns"></i></button>
|
||||
<button type="button" class="assistant-menu-toggle" data-menu-toggle aria-expanded="true" title="Hide menu" aria-label="Hide menu"><i class="fas fa-bars"></i></button>
|
||||
<!-- The app IS workspace mode. One switch, shown in both places and
|
||||
styled identically, so moving between them is a highlight change
|
||||
rather than a different-looking screen. -->
|
||||
|
|
|
|||
|
|
@ -60,8 +60,9 @@ test('assistant area is an OWUI-style three-column workspace with a slim go-back
|
|||
assert.equal(right.querySelector('#assistant-visual-output'), null, 'no image display in the right column — sources only');
|
||||
assert.ok(right.querySelector('#assistant-sources'), 'sources panel in the right column');
|
||||
assert.equal(right.querySelector('#assistant-saved-chats'), null, 'saved chats moved out of the right column');
|
||||
const topbar = app.document.querySelector('.assistant-topbar');
|
||||
assert.ok(topbar);
|
||||
// No bar above the layout at all: the app page has only the blue header, so a
|
||||
// second bar here made the assistant taller and left dead space on top.
|
||||
assert.equal(app.document.querySelector('.assistant-topbar'), null, 'no assistant-only top bar');
|
||||
// Go back is gone: the Assistant/Workspace switch is how you leave, so a
|
||||
// second exit control in the topbar was one affordance too many.
|
||||
assert.equal(app.document.querySelector('#btn-assistant-goback'), null, 'no Go back in the topbar');
|
||||
|
|
@ -154,15 +155,31 @@ test('collapsing the rail does not hand the chat column to the citations panel',
|
|||
assert.match(track, /grid-template-columns:0 minmax\(0,1fr\) 330px/, 'three tracks for three items');
|
||||
});
|
||||
|
||||
test('the assistant workspace does not repeat its own name in the topbar', () => {
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const css = fs.readFileSync(path.join(__dirname, '..', 'public/css/assistant.css'), 'utf8');
|
||||
const rule = css.split('\n').find(l => l.startsWith('body.assistant-workspace .assistant-topbar-title h2'));
|
||||
assert.ok(rule, 'the title is hidden inside the workspace');
|
||||
assert.match(rule, /clip:rect\(0 0 0 0\)/, 'visually hidden, still read by screen readers');
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'public/components/assistant.html'), 'utf8');
|
||||
assert.match(html, /AI Clinical Assistant/, 'and still present in the DOM');
|
||||
test('the assistant and the app start at the same top edge', () => {
|
||||
const rail = read('public/components/assistant.html');
|
||||
const css = read('public/css/assistant.css');
|
||||
// The assistant used to carry its own bar on top of the shared blue header,
|
||||
// so switching modes changed the height of everything below it.
|
||||
assert.doesNotMatch(rail, /assistant-topbar/, 'no second bar in the markup');
|
||||
assert.doesNotMatch(css, /assistant-topbar/, 'and none left in the stylesheet');
|
||||
assert.match(css, /\.assistant-history \{ display:grid; gap:10px; align-content:start; position:sticky; top:0; \}/,
|
||||
'the rail sticks to the top of the layout, not below a bar');
|
||||
// The status moved into the composer, next to the work it describes.
|
||||
assert.match(rail, /assistant-composer-footer[\s\S]*?id="assistant-status"/,
|
||||
'the ready/busy indicator lives in the composer now');
|
||||
});
|
||||
|
||||
test('the menu toggle uses an icon that reads as a menu', () => {
|
||||
for (const file of ['public/index.html', 'public/components/assistant.html']) {
|
||||
const markup = read(file);
|
||||
const button = markup.split('\n').find(l => l.includes('data-menu-toggle'));
|
||||
assert.ok(button, file + ' has the toggle');
|
||||
// fa-table-columns draws a split-pane glyph, which does not say "menu".
|
||||
assert.doesNotMatch(button, /fa-table-columns/, file + ' uses a menu icon');
|
||||
assert.match(button, /fa-bars/, file);
|
||||
}
|
||||
assert.match(read('public/css/assistant.css'), /\.assistant-menu-toggle\.is-collapsed i \{ transform:rotate\(180deg\); \}/,
|
||||
'and it turns to show the direction it will act in');
|
||||
});
|
||||
|
||||
test('workspace mode does not override the mobile layout with a desktop header offset', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue