.sidebar sets overflow:hidden and the collapsed rail is 52px wide, so the account menu — laid out inside it — was cut to a sliver. Settings, FAQ and Log out became unreachable behind the avatar exactly when the rail is narrow, which is the state the screenshot shows. The collapsed menu is now positioned against the viewport instead of the rail, so it cannot be clipped by the thing that opens it. The card also sat lower in the assistant than in the app: the app column pads its bottom and the assistant rail only padded the top. Both now use the same vertical padding and the same row height, so the card lands on one baseline in either view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0161xNW1z4vPusrXKGWcQdQu
83 lines
4.7 KiB
JavaScript
83 lines
4.7 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const read = f => fs.readFileSync(path.join(__dirname, '..', f), 'utf8');
|
|
|
|
// On a phone the assistant showed TWO hamburgers, stacked, and neither did
|
|
// anything useful: the app's opened a sidebar the assistant had hidden, and the
|
|
// assistant's opened a rail that duplicated it.
|
|
test('a phone has exactly one menu button', () => {
|
|
const assistant = read('public/components/assistant.html');
|
|
const index = read('public/index.html');
|
|
assert.doesNotMatch(assistant, /btn-assistant-mobile-menu/,
|
|
'the assistant no longer adds a drawer button of its own');
|
|
assert.equal((index.match(/id="btn-menu-toggle"/g) || []).length, 1,
|
|
'the app sidebar button is the only one');
|
|
});
|
|
|
|
test('the app sidebar stays reachable inside the assistant on a phone', () => {
|
|
const css = read('public/css/styles.css');
|
|
// Hiding it unconditionally is what made the app hamburger open nothing.
|
|
assert.match(css, /@media\(min-width:769px\)\{ body\.assistant-workspace \.sidebar\{ display:none; \} \}/,
|
|
'the sidebar is only replaced by the assistant rail on desktop');
|
|
const hideBlock = css.slice(css.indexOf('body.assistant-workspace .announcement-banner'), css.indexOf('body.assistant-workspace .app-body'));
|
|
assert.doesNotMatch(hideBlock, /\.sidebar/, 'nothing hides it unconditionally');
|
|
});
|
|
|
|
test('the collapse control closes the sheet on a phone rather than collapsing a rail', () => {
|
|
const app = read('public/js/app.js');
|
|
const handler = app.slice(app.indexOf("event.target.closest('[data-menu-toggle]')"), app.indexOf("localStorage.setItem('ped_sidebar_collapsed'"));
|
|
assert.match(handler, /if \(window\.innerWidth <= 768\)/);
|
|
assert.match(handler, /sidebar\.classList\.remove\('open'\)/, 'it closes the sheet');
|
|
});
|
|
|
|
test('sources are reachable on a phone instead of deleted', () => {
|
|
const css = read('public/css/assistant.css');
|
|
const mobile = css.slice(css.indexOf('@media (max-width: 640px)'));
|
|
// display:none left every citation pointing at nothing.
|
|
assert.doesNotMatch(mobile.slice(0, 2000), /\.assistant-side \{ display:none; \}/);
|
|
assert.match(mobile, /body\.assistant-sources-open \.assistant-side \{ transform:translateY\(0\); \}/,
|
|
'they slide up as a sheet');
|
|
|
|
const js = read('public/js/clinicalAssistant.js');
|
|
assert.match(js, /button\.hidden = !\(citationsOn && count > 0\);/,
|
|
'the opener appears only when an answer has citations');
|
|
assert.match(js, /event\.target\.closest\('\.assistant-cite'\)/, 'and tapping a citation opens it');
|
|
// A sheet with no way out is a trap.
|
|
assert.match(js, /if \(event\.key !== 'Escape'\) return;[\s\S]{0,160}assistant-sources-open/);
|
|
assert.match(js, /if \(!document\.body\.classList\.contains\('assistant-sources-open'\)\) return;/,
|
|
'tapping away closes it');
|
|
});
|
|
|
|
test('the mobile menu is sized for a thumb', () => {
|
|
const css = read('public/css/styles.css');
|
|
const mobile = css.slice(css.indexOf('/* ── Mobile menu ─'));
|
|
assert.match(mobile, /\.sidebar\{ width:min\(88vw,360px\)/, 'a sheet, not a narrow rail');
|
|
assert.match(mobile, /\.tab-btn\{ padding:12px 10px; font-size:14\.5px/, 'rows are tappable');
|
|
assert.match(mobile, /env\(safe-area-inset-bottom\)/, 'and clear of the home indicator');
|
|
});
|
|
|
|
test('the account menu escapes the collapsed rail instead of being clipped', () => {
|
|
const css = read('public/css/styles.css');
|
|
// .sidebar has overflow:hidden and the collapsed rail is 52px, so a menu laid
|
|
// out inside it was cut to a sliver — hiding Settings, FAQ and Log out behind
|
|
// the avatar exactly when the rail is narrow.
|
|
const sidebar = css.split('\n').find(l => l.startsWith('.sidebar{'));
|
|
assert.match(sidebar, /overflow:hidden/, 'the rail does clip, so the menu must escape it');
|
|
assert.match(css, /body\.menu-hidden \.account-menu \{ position:fixed;/,
|
|
'the collapsed menu is positioned against the viewport, not the rail');
|
|
assert.match(css, /body\.menu-hidden \.account-menu \{[^}]*width:210px/, 'and keeps a readable width');
|
|
});
|
|
|
|
test('the account card sits at the same height in both rails', () => {
|
|
const css = read('public/css/styles.css');
|
|
// The app column pads its bottom; the assistant rail did not, so its card
|
|
// started lower than the app's.
|
|
const app = css.split('\n').find(l => l.startsWith('.account-card {'));
|
|
const assistant = css.split('\n').find(l => l.startsWith('.assistant-history .account-card {'));
|
|
assert.match(app, /padding:8px;/);
|
|
assert.match(assistant, /padding:8px 0;/, 'same vertical padding, so the cards align');
|
|
assert.match(css, /\.account-card-btn \{ min-height:44px; \}/,
|
|
'and the same row height in both');
|
|
});
|