fix: style the shared chrome from the stylesheet the app page actually loads
The Assistant/Workspace switch and the menu toggle rendered as unstyled native buttons in the app sidebar. Their CSS was in assistant.css, which is pulled in by the assistant COMPONENT — so it is simply absent on "/", where those controls now also render. Both moved to styles.css, which every page loads. A stale duplicate of the switch rules was also still sitting in assistant.css; that is exactly how two copies drift and one silently wins, so it is gone. Adds a test that walks the classes rendered in index.html and asserts each is defined in styles.css and NOT duplicated in assistant.css, which would have caught this before it shipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjVc5oaAaYFNbZGLeJp6TX
This commit is contained in:
parent
18cee14549
commit
6c2bafbe02
3 changed files with 54 additions and 36 deletions
|
|
@ -154,12 +154,6 @@ body.assistant-workspace .assistant-topbar-title h2 { position:absolute; width:1
|
|||
.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; }
|
||||
.assistant-mode-switch button i { font-size:11px; }
|
||||
.assistant-mode-switch button.active { background:white; color:var(--blue); box-shadow:var(--shadow); }
|
||||
|
||||
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; }
|
||||
/* On a phone the rail IS the drawer menu; a second copy as cards is redundant
|
||||
|
|
@ -448,33 +442,6 @@ body.assistant-workspace #assistant-chat-view { min-height: 0; }
|
|||
.assistant-history .card-header { padding:2px 4px; }
|
||||
.assistant-history .card-header h3 { font-size:11px; font-weight:700; color:var(--g500); text-transform:uppercase; letter-spacing:.04em; }
|
||||
|
||||
/* ── Assistant / Workspace switch ────────────────────────────────────────────
|
||||
Rendered identically in the app sidebar and the assistant rail, so moving
|
||||
between them changes which pill is highlighted and nothing else. */
|
||||
.assistant-mode-switch { display:grid; grid-template-columns:1fr 1fr; gap:4px; margin:8px 12px 10px; padding:4px; background:var(--g100); border:1px solid var(--g200); border-radius:10px; }
|
||||
.assistant-mode-switch button { display:flex; align-items:center; justify-content:center; gap:6px; border:0; border-radius:7px; padding:7px 6px; font-family:inherit; font-size:12px; font-weight:600; color:var(--g600); background:transparent; cursor:pointer; }
|
||||
.assistant-mode-switch button i { font-size:11px; }
|
||||
.assistant-mode-switch button:hover { color:var(--g900); }
|
||||
.assistant-mode-switch button.active { background:white; color:var(--blue); box-shadow:var(--shadow); }
|
||||
/* Inside the assistant rail the gutters already exist, so it only needs the gap. */
|
||||
.assistant-history .assistant-mode-switch { margin:0 0 8px; }
|
||||
|
||||
/* The one menu toggle, identical in the app sidebar and the assistant rail. */
|
||||
.assistant-menu-toggle { display:flex; align-items:center; justify-content:center; width:32px; height:32px; margin:10px 12px 0; border:1px solid var(--g200); border-radius:8px; background:white; color:var(--g500); font-size:12px; cursor:pointer; }
|
||||
.assistant-menu-toggle:hover { color:var(--blue); border-color:var(--blue-light); background:var(--blue-light); }
|
||||
.assistant-history .assistant-menu-toggle { margin:0 0 8px; }
|
||||
|
||||
/* Hidden menu: the rail collapses in both places, and the layout takes the
|
||||
space back rather than leaving a gutter. */
|
||||
body.menu-hidden .sidebar { width:0; border:none; overflow:hidden; }
|
||||
body.assistant-workspace.menu-hidden .assistant-layout { grid-template-columns:0 minmax(0,1fr) 330px; }
|
||||
body.assistant-workspace.menu-hidden.assistant-no-citations .assistant-layout { grid-template-columns:0 minmax(0,1fr); }
|
||||
body.assistant-workspace.menu-hidden .assistant-history > *:not(.assistant-menu-toggle) { display:none; }
|
||||
body.assistant-workspace.menu-hidden .assistant-history { width:0; min-width:0; overflow:visible; padding:0; }
|
||||
/* The toggle must survive its own collapse, or there is no way back. */
|
||||
body.assistant-workspace.menu-hidden .assistant-history .assistant-menu-toggle { position:absolute; left:8px; top:8px; z-index:5; }
|
||||
@media (max-width:640px) { .assistant-menu-toggle { display:none; } body.menu-hidden .sidebar { width:auto; } }
|
||||
|
||||
/* Workspace launcher: same ground, card language and spacing as the empty state,
|
||||
so the two views are one interface. Desktop only. */
|
||||
.assistant-workspace-view { flex:1 1 auto; min-height:0; padding:24px 18px; overflow-y:auto;
|
||||
|
|
|
|||
|
|
@ -1209,3 +1209,34 @@ body.assistant-workspace .app-body { min-height:100vh; }
|
|||
body.assistant-workspace #assistant-tab.active { width:100%; }
|
||||
/* iOS: taps on controls must not trigger double-tap zoom */
|
||||
button, a, .btn-sm, .btn-generate, .btn-send, .tab-btn, input, textarea, select, label { touch-action: manipulation; }
|
||||
|
||||
/* ── Shared app chrome: the Assistant/Workspace switch and the menu toggle ────
|
||||
These render in BOTH the app sidebar and the assistant rail. They used to live
|
||||
in assistant.css, which only the assistant component loads — so on the main
|
||||
app page they rendered as unstyled native buttons. */
|
||||
/* ── Assistant / Workspace switch ────────────────────────────────────────────
|
||||
Rendered identically in the app sidebar and the assistant rail, so moving
|
||||
between them changes which pill is highlighted and nothing else. */
|
||||
.assistant-mode-switch { display:grid; grid-template-columns:1fr 1fr; gap:4px; margin:8px 12px 10px; padding:4px; background:var(--g100); border:1px solid var(--g200); border-radius:10px; }
|
||||
.assistant-mode-switch button { display:flex; align-items:center; justify-content:center; gap:6px; border:0; border-radius:7px; padding:7px 6px; font-family:inherit; font-size:12px; font-weight:600; color:var(--g600); background:transparent; cursor:pointer; }
|
||||
.assistant-mode-switch button i { font-size:11px; }
|
||||
.assistant-mode-switch button:hover { color:var(--g900); }
|
||||
.assistant-mode-switch button.active { background:white; color:var(--blue); box-shadow:var(--shadow); }
|
||||
/* Inside the assistant rail the gutters already exist, so it only needs the gap. */
|
||||
.assistant-history .assistant-mode-switch { margin:0 0 8px; }
|
||||
|
||||
/* The one menu toggle, identical in the app sidebar and the assistant rail. */
|
||||
.assistant-menu-toggle { display:flex; align-items:center; justify-content:center; width:32px; height:32px; margin:10px 12px 0; border:1px solid var(--g200); border-radius:8px; background:white; color:var(--g500); font-size:12px; cursor:pointer; }
|
||||
.assistant-menu-toggle:hover { color:var(--blue); border-color:var(--blue-light); background:var(--blue-light); }
|
||||
.assistant-history .assistant-menu-toggle { margin:0 0 8px; }
|
||||
|
||||
/* Hidden menu: the rail collapses in both places, and the layout takes the
|
||||
space back rather than leaving a gutter. */
|
||||
body.menu-hidden .sidebar { width:0; border:none; overflow:hidden; }
|
||||
body.assistant-workspace.menu-hidden .assistant-layout { grid-template-columns:0 minmax(0,1fr) 330px; }
|
||||
body.assistant-workspace.menu-hidden.assistant-no-citations .assistant-layout { grid-template-columns:0 minmax(0,1fr); }
|
||||
body.assistant-workspace.menu-hidden .assistant-history > *:not(.assistant-menu-toggle) { display:none; }
|
||||
body.assistant-workspace.menu-hidden .assistant-history { width:0; min-width:0; overflow:visible; padding:0; }
|
||||
/* The toggle must survive its own collapse, or there is no way back. */
|
||||
body.assistant-workspace.menu-hidden .assistant-history .assistant-menu-toggle { position:absolute; left:8px; top:8px; z-index:5; }
|
||||
@media (max-width:640px) { .assistant-menu-toggle { display:none; } body.menu-hidden .sidebar { width:auto; } }
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ test('one menu toggle serves both the app sidebar and the assistant rail', () =>
|
|||
assert.match(app, /classList\.toggle\('menu-hidden'\)/, 'one class drives both');
|
||||
assert.match(app, /'Show menu' : 'Hide menu'/, 'the label flips so the collapsed state is escapable');
|
||||
|
||||
const css = read('public/css/assistant.css');
|
||||
// Shared chrome lives in styles.css: the app page never loads assistant.css.
|
||||
const css = read('public/css/styles.css');
|
||||
assert.match(css, /body\.menu-hidden \.sidebar \{ width:0/);
|
||||
assert.match(css, /body\.assistant-workspace\.menu-hidden \.assistant-history \.assistant-menu-toggle \{ position:absolute/,
|
||||
'the toggle survives its own collapse, or there is no way back');
|
||||
|
|
@ -249,12 +250,31 @@ test('the assistant uses the app accent, so neither screen looks like a differen
|
|||
assert.match(styles, /\.tab-btn\.active\{background:var\(--blue-light\);color:var\(--blue\)/,
|
||||
'the app accent is blue');
|
||||
|
||||
const css = read('public/css/assistant.css');
|
||||
assert.match(css, /\.assistant-mode-switch button\.active \{ background:white; color:var\(--blue\)/,
|
||||
assert.match(read('public/css/styles.css'), /\.assistant-mode-switch button\.active \{ background:white; color:var\(--blue\)/,
|
||||
'the switch highlights in the app accent');
|
||||
const css = read('public/css/assistant.css');
|
||||
assert.match(css, /\.assistant-msg\.user \.assistant-bubble \{ white-space:pre-wrap; background:var\(--blue\)/);
|
||||
// Citations keep their own purple chip so evidence stays distinct from
|
||||
// interactive blue; nothing else may reintroduce a second accent.
|
||||
const stray = css.split('\n').filter(l => l.includes('var(--purple') && !l.includes('assistant-cite'));
|
||||
assert.deepEqual(stray, [], 'no second accent outside citation chips');
|
||||
});
|
||||
|
||||
test('chrome rendered on the main app page is styled by the stylesheet that page loads', () => {
|
||||
const index = read('public/index.html');
|
||||
const styles = read('public/css/styles.css');
|
||||
const assistantCss = read('public/css/assistant.css');
|
||||
|
||||
// assistant.css is pulled in by the assistant COMPONENT, so it is absent on
|
||||
// "/". Anything the app page renders must be styled from styles.css or it
|
||||
// shows up as an unstyled native control.
|
||||
assert.doesNotMatch(index, /css\/assistant\.css/, 'the app page does not load assistant.css');
|
||||
|
||||
for (const cls of ['assistant-mode-switch', 'assistant-menu-toggle']) {
|
||||
assert.match(index, new RegExp('class="[^"]*' + cls), cls + ' is rendered on the app page');
|
||||
assert.ok(styles.includes('.' + cls + ' {'), cls + ' must be defined in styles.css');
|
||||
// Defined in both files, the two copies drift and one silently wins.
|
||||
assert.ok(!assistantCss.includes('.' + cls + ' {'), cls + ' must not be duplicated in assistant.css');
|
||||
}
|
||||
assert.match(styles, /body\.menu-hidden \.sidebar \{ width:0/, 'the collapse state is styled where the app can see it');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue