fix: collapsed strip, workspace-mode search, image dialog chat list
- Collapsed menu: its own plain white 52px column against the tiled ground, no border line, in both views. Flat, it had no edge. - Collapsed + workspace launcher: assistant.css set the rail track to 0 and beat the 52px rule, so the collapsed menu (name card included) vanished. - Search follows the view, not the URL: the workspace launcher keeps /assistant, so Workspace searched chats. - Create image, "Base it on": a native <select> list is as wide as its longest option, so full titles pushed it past the dialog. Labels now keep the whole words that fit the select's width; the full title is on hover. Verified in Chromium: account menu items on top in all 8 states; collapsed strip 52px white with the name card at the same place in all 3 views; search placeholder and results per mode; dropdown widest label 500px in a 506px box (279 in 280 on a phone). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4rptBNvn6RYieQw54GXNS
This commit is contained in:
parent
8756c9a140
commit
f93994ad69
5 changed files with 89 additions and 3 deletions
|
|
@ -472,7 +472,10 @@ body.assistant-mode-workspace .assistant-history .card,
|
|||
body.assistant-mode-workspace .assistant-rail-actions,
|
||||
body.assistant-mode-workspace .assistant-side { display:none; }
|
||||
body.assistant-mode-workspace .assistant-layout { grid-template-columns:210px minmax(0,1fr); }
|
||||
body.assistant-mode-workspace.menu-hidden .assistant-layout { grid-template-columns:0 minmax(0,1fr); }
|
||||
/* Collapsed is the 52px strip in workspace mode too. This said 0, and loading
|
||||
after styles.css it beat the 52px rule there, so the collapsed menu (name
|
||||
card included) vanished whenever the workspace launcher was showing. */
|
||||
body.assistant-mode-workspace.menu-hidden .assistant-layout { grid-template-columns:52px minmax(0,1fr); }
|
||||
/* Workspace mode works on a phone too: the launcher is the same grid of cards,
|
||||
one per column. Suppressing it here is what made the Workspace pill do
|
||||
nothing at all on mobile. */
|
||||
|
|
|
|||
|
|
@ -1311,6 +1311,12 @@ button, a, .btn-sm, .btn-generate, .btn-send, .tab-btn, input, textarea, select,
|
|||
The phone sheet has nothing to collapse — the same control closes it —
|
||||
so none of this may leak past the breakpoint. */
|
||||
body.menu-hidden .sidebar { width:52px; }
|
||||
/* Collapsed, the strip is a column of its own: plain white against the tiled
|
||||
ground, no drawn border — the white is the edge. Flat on the tiles, the 52px
|
||||
column had no edge and read as icons pressed against the page card. The
|
||||
full-width menu stays flat. Same strip in both views. */
|
||||
body.menu-hidden .sidebar,
|
||||
body.assistant-workspace.menu-hidden .assistant-history { background:white; }
|
||||
body.menu-hidden .sidebar-tabs,
|
||||
body.menu-hidden .assistant-mode-switch,
|
||||
body.menu-hidden .sidebar-section-label,
|
||||
|
|
|
|||
|
|
@ -314,8 +314,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
}
|
||||
|
||||
function searchSources() {
|
||||
// The assistant publishes its chats; anything else searches the menu.
|
||||
if (window.location.pathname === '/assistant' && typeof window.assistantSearchableChats === 'function') {
|
||||
// Search follows the view, not the address: the assistant's chat view
|
||||
// searches chats, and Workspace — including the workspace launcher shown
|
||||
// inside the assistant, which keeps the /assistant URL — searches the menu.
|
||||
var inChats = document.body.classList.contains('assistant-workspace')
|
||||
&& !document.body.classList.contains('assistant-mode-workspace');
|
||||
if (inChats && typeof window.assistantSearchableChats === 'function') {
|
||||
return { kind: 'chats', items: window.assistantSearchableChats() };
|
||||
}
|
||||
var items = [];
|
||||
|
|
|
|||
|
|
@ -1350,6 +1350,7 @@ import {
|
|||
'<div id="create-image-history" class="create-image-history"><p class="assistant-muted">Loading your images…</p></div>' +
|
||||
'</div></div>';
|
||||
document.body.appendChild(modal);
|
||||
fitSelectOptions(modal.querySelector('#create-image-chat'));
|
||||
modal.addEventListener('click', function (event) {
|
||||
if (event.target === modal || event.target.closest('[data-create-image-close]')) modal.remove();
|
||||
});
|
||||
|
|
@ -2391,6 +2392,36 @@ import {
|
|||
return truncateOnWord(String(first && first.content || 'Clinical assistant chat').replace(/\s+/g, ' ').trim(), 160);
|
||||
}
|
||||
|
||||
// A native <select> opens a list as wide as its longest option, so full chat
|
||||
// titles pushed "Base it on" far past the dialog. Each label keeps as many
|
||||
// whole words as fit the select's own width; the full title stays on hover.
|
||||
var OPTION_CHROME = 44; // list padding + scrollbar, which the text cannot use
|
||||
function fitSelectOptions(select) {
|
||||
if (!select) return;
|
||||
var style = window.getComputedStyle(select);
|
||||
var room = select.clientWidth - OPTION_CHROME;
|
||||
if (room <= 0) return; // not laid out (or no layout engine): leave labels alone
|
||||
var ctx = fitSelectOptions.ctx || (fitSelectOptions.ctx = document.createElement('canvas').getContext('2d'));
|
||||
if (!ctx) return;
|
||||
ctx.font = style.fontWeight + ' ' + style.fontSize + ' ' + style.fontFamily;
|
||||
Array.prototype.forEach.call(select.options, function (opt) {
|
||||
var full = opt.getAttribute('data-full-title') || opt.textContent;
|
||||
opt.setAttribute('data-full-title', full);
|
||||
opt.title = full;
|
||||
if (ctx.measureText(full).width <= room) { opt.textContent = full; return; }
|
||||
var words = full.split(/\s+/), text = '';
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
var next = text ? text + ' ' + words[i] : words[i];
|
||||
if (ctx.measureText(next + '…').width > room) break;
|
||||
text = next;
|
||||
}
|
||||
// One word wider than the box: fall back to cutting characters.
|
||||
if (!text) { text = full; while (text.length > 1 && ctx.measureText(text + '…').width > room) text = text.slice(0, -1); }
|
||||
opt.textContent = text.replace(/[\s.,;:!?-]+$/, '') + '…';
|
||||
});
|
||||
}
|
||||
window.addEventListener('resize', function () { fitSelectOptions(document.getElementById('create-image-chat')); });
|
||||
|
||||
// Cuts at a word boundary, and only adds an ellipsis when something was
|
||||
// actually dropped.
|
||||
function truncateOnWord(text, limit) {
|
||||
|
|
|
|||
|
|
@ -568,3 +568,45 @@ test('desktop: the assistant rail is the app sidebar, flush and flat', () => {
|
|||
assert.match(rail, /\.assistant-history \.account-card \{ padding:8px; \}/);
|
||||
assert.match(styles, /body\.assistant-workspace\.menu-hidden \.assistant-history \{[^}]*padding:0; \}/, 'collapsed adds no gap of its own');
|
||||
});
|
||||
|
||||
test('the collapsed menu is a bar of its own, in both views', () => {
|
||||
const css = read('public/css/styles.css');
|
||||
// Flat on the tiled ground, the 52px strip had no edge: its icons looked
|
||||
// pressed against the page card. Expanded stays flat; collapsed gets a bar.
|
||||
const desk = css.slice(css.indexOf('@media (min-width:769px) {\n /* Collapsing is a desktop idea'));
|
||||
const block = desk.slice(0, desk.indexOf('\n}\n'));
|
||||
// Its own white column, no border line: the white against the tiles is the edge.
|
||||
assert.match(block, /body\.menu-hidden \.sidebar,\s*body\.assistant-workspace\.menu-hidden \.assistant-history \{ background:white; \}/);
|
||||
assert.match(css, /@media\(min-width:769px\)\{ \.sidebar\{ background:transparent; border-right:none; \} \}/, 'expanded stays flat');
|
||||
});
|
||||
|
||||
test('search follows the view: Workspace searches the workspace even inside the assistant', () => {
|
||||
const app = read('public/js/app.js');
|
||||
const fn = app.slice(app.indexOf('function searchSources()'), app.indexOf("return { kind: 'tabs'"));
|
||||
// Keyed on the URL, the workspace launcher (still at /assistant) searched chats.
|
||||
assert.doesNotMatch(fn, /location\.pathname === '\/assistant'/);
|
||||
assert.match(fn, /classList\.contains\('assistant-workspace'\)\s*&& !document\.body\.classList\.contains\('assistant-mode-workspace'\)/);
|
||||
assert.match(fn, /if \(inChats && typeof window\.assistantSearchableChats === 'function'\)/);
|
||||
});
|
||||
|
||||
test('collapsed workspace mode keeps the 52px strip instead of losing the menu', () => {
|
||||
const css = read('public/css/assistant.css');
|
||||
// This track said 0 and, loading after styles.css, won — the collapsed menu
|
||||
// and its name card disappeared whenever the workspace launcher showed.
|
||||
assert.doesNotMatch(css, /body\.assistant-mode-workspace\.menu-hidden \.assistant-layout \{ grid-template-columns:0 /);
|
||||
assert.match(css, /body\.assistant-mode-workspace\.menu-hidden \.assistant-layout \{ grid-template-columns:52px minmax\(0,1fr\); \}/);
|
||||
});
|
||||
|
||||
test('the create-image chat list never grows wider than its dialog', () => {
|
||||
const js = read('public/js/clinicalAssistant.js');
|
||||
// A native <select> list is as wide as its longest option; full chat titles
|
||||
// pushed it far past the dialog.
|
||||
assert.match(js, /document\.body\.appendChild\(modal\);\s*\n\s*fitSelectOptions\(modal\.querySelector\('#create-image-chat'\)\);/,
|
||||
'labels are fitted once the dialog is laid out');
|
||||
const fn = js.slice(js.indexOf('function fitSelectOptions(select)'), js.indexOf("window.addEventListener('resize', function () { fitSelectOptions"));
|
||||
assert.match(fn, /var room = select\.clientWidth - OPTION_CHROME;/, 'fitted to the select, not a fixed character count');
|
||||
assert.match(fn, /if \(room <= 0\) return;/, 'no layout, no change');
|
||||
assert.match(fn, /opt\.title = full;/, 'the full title stays on hover');
|
||||
assert.match(fn, /words\[i\]/, 'cuts on whole words');
|
||||
assert.match(js, /window\.addEventListener\('resize', function \(\) \{ fitSelectOptions\(document\.getElementById\('create-image-chat'\)\); \}\);/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue