style: flat menu on the tiled ground in both views; tiled page cards

- The tiled ground moves from .main-content to .app-body, so the menu sits
  on it too and the grid keeps one origin whichever view is showing. A
  second tile on .main-content would have started 210px in and seamed.
- Desktop: the workspace sidebar is flat (transparent, no border), the same
  as the assistant rail, which drops the white box it briefly had. The phone
  sheets stay white because they slide over the page.
- Workspace page cards get the assistant chat panel's tiled-white fill.

Measured in Chromium: name card 848-892 at x=8, brand, switch and 210px
width identical in both views, expanded and collapsed; no page scroll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4rptBNvn6RYieQw54GXNS
This commit is contained in:
Daniel 2026-09-10 12:23:31 +02:00
parent f3a2309536
commit 8756c9a140
3 changed files with 42 additions and 16 deletions

View file

@ -563,17 +563,17 @@ body.assistant-mode-workspace .assistant-rail-workspace { display:flex; }
body.assistant-sources-open .assistant-sources-fab { background:var(--blue-light); border-color:var(--blue-light); } body.assistant-sources-open .assistant-sources-fab { background:var(--blue-light); border-color:var(--blue-light); }
} }
/* Desktop: the rail is the same white sidebar the workspace uses, flush to the /* Desktop: the rail is laid out as the workspace sidebar is, flush to the
window edge. It used to sit inside the content area's 14px padding, on the window edge and flat on the page's tiled ground. It used to sit inside the
tiled ground so the menu changed background between the two views, the content area's 14px padding, so its name card sat 6px lower and further in
name card sat 6px lower and further in, and the page was 28px taller than and the page was 28px taller than the window. The 14px now belongs to the
the window. The 14px now belongs to the panels beside the rail. */ panels beside the rail. */
@media (min-width:961px) { @media (min-width:961px) {
body.assistant-workspace .main-content { padding:0; } body.assistant-workspace .main-content { padding:0; }
body.assistant-workspace .assistant-layout { padding-right:14px; } body.assistant-workspace .assistant-layout { padding-right:14px; }
body.assistant-workspace .assistant-main, body.assistant-workspace .assistant-main,
body.assistant-workspace .assistant-side { margin-block:14px; height:auto; } body.assistant-workspace .assistant-side { margin-block:14px; height:auto; }
body.assistant-workspace .assistant-history { padding:0; gap:0; background:white; border-right:1.5px solid var(--g200); } body.assistant-workspace .assistant-history { padding:0; gap:0; }
body.assistant-workspace .assistant-rail-actions { padding:0 8px; } body.assistant-workspace .assistant-rail-actions { padding:0 8px; }
body.assistant-workspace .assistant-history .card { padding:0 8px; } body.assistant-workspace .assistant-history .card { padding:0 8px; }
/* Workspace links: the app's .tab-btn, full-bleed with the same inset. */ /* Workspace links: the app's .tab-btn, full-bleed with the same inset. */

View file

@ -66,11 +66,20 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
} }
/* APP BODY LAYOUT */ /* APP BODY LAYOUT */
.app-body{display:flex;min-height:100vh;} /* The tiled ground covers the whole page menu and content alike from one
origin, so switching between the assistant and a workspace page never moves
the grid or changes what the menu sits on. */
.app-body{display:flex;min-height:100vh;
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;background-color:var(--g50);}
/* SIDEBAR */ /* SIDEBAR */
.sidebar{width:210px;flex-shrink:0;background:white;border-right:1.5px solid var(--g200);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow:hidden;z-index:100;transition:width 0.22s ease;} .sidebar{width:210px;flex-shrink:0;background:white;border-right:1.5px solid var(--g200);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow:hidden;z-index:100;transition:width 0.22s ease;}
.sidebar.collapsed{width:0;border:none;overflow:hidden;} .sidebar.collapsed{width:0;border:none;overflow:hidden;}
/* Desktop: the menu is flat on the tiled ground, as the assistant's rail is
a white boxed sidebar here made every workspace page look like another app.
A phone keeps the white sheet, because there it slides over the page. */
@media(min-width:769px){ .sidebar{ background:transparent; border-right:none; } }
.sidebar-header{display:none;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--g200);} .sidebar-header{display:none;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--g200);}
.sidebar-title{font-size:13px;font-weight:700;color:var(--g700);} .sidebar-title{font-size:13px;font-weight:700;color:var(--g700);}
/* Head pinned, list scrolls, account card pinned. */ /* Head pinned, list scrolls, account card pinned. */
@ -89,12 +98,14 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
/* MAIN */ /* MAIN */
/* One shell for every view. Each tab used to be a plain white page while the /* One shell for every view. Each tab used to be a plain white page while the
assistant and the workspace launcher sat on a tiled card, so opening any menu assistant and the workspace launcher sat on a tiled card, so opening any menu
item changed the whole face of the app. The tiled ground and the card edge item changed the whole face of the app. The card edge belongs to the shell
belong to the shell, so only the contents differ. */ and the tiled ground to .app-body, so only the contents differ. */
.main-content{flex:1;min-width:0;min-height:0;padding:14px;display:flex;flex-direction:column; .main-content{flex:1;min-width:0;min-height:0;padding:14px;display:flex;flex-direction:column;}
/* The page card carries the same tiled-white fill as the assistant's chat panel
(.assistant-main.card), so entering a workspace page keeps the surface. */
.main-content > .tab-content.active{flex:1 1 auto;min-height:0;background-color:white;
background-image:linear-gradient(var(--g100) 1px, transparent 1px), linear-gradient(90deg, var(--g100) 1px, transparent 1px); 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;background-color:var(--g50);} background-size:28px 28px;background-position:-1px -1px;border:1px solid var(--g200);border-radius:14px;box-shadow:var(--shadow);padding:20px;overflow:auto;}
.main-content > .tab-content.active{flex:1 1 auto;min-height:0;background:white;border:1px solid var(--g200);border-radius:14px;box-shadow:var(--shadow);padding:20px;overflow:auto;}
/* The assistant brings its own full-bleed layout, so it takes the shell's place /* The assistant brings its own full-bleed layout, so it takes the shell's place
rather than nesting a second card inside it. */ rather than nesting a second card inside it. */
.main-content > #assistant-tab.active{padding:0;border:none;box-shadow:none;background:transparent;overflow:visible;} .main-content > #assistant-tab.active{padding:0;border:none;box-shadow:none;background:transparent;overflow:visible;}

View file

@ -423,10 +423,24 @@ test('every view sits in the same shell, so opening a menu item is not a new app
const css = read('public/css/styles.css'); const css = read('public/css/styles.css');
// Each tab used to be a plain white page while the assistant and the workspace // Each tab used to be a plain white page while the assistant and the workspace
// launcher sat on a tiled card, so any menu click changed the whole face. // launcher sat on a tiled card, so any menu click changed the whole face.
assert.match(css, /\.main-content\{[^}]*background-image:linear-gradient/, // The ground covers the whole page, menu included, from one origin: a second
'the tiled ground belongs to the shell'); // tile on .main-content would start 210px in and seam against the menu's.
assert.match(css, /\.app-body\{[^}]*background-image:linear-gradient/,
'the tiled ground belongs to the page');
assert.doesNotMatch(css, /\.main-content\{[^}]*background-image/, 'and only to the page');
// Flat menu on desktop, in both views; the phone sheet stays white.
assert.match(css, /@media\(min-width:769px\)\{ \.sidebar\{ background:transparent; border-right:none; \} \}/);
assert.match(css, /\.main-content > \.tab-content\.active\{[^}]*border:1px solid var\(--g200\);border-radius:14px/, assert.match(css, /\.main-content > \.tab-content\.active\{[^}]*border:1px solid var\(--g200\);border-radius:14px/,
'and every tab gets the same card edge'); 'and every tab gets the same card edge');
// ...and the same tiled-white surface as the assistant's chat panel, so a
// workspace page does not turn plain white on entry.
const tile = 'linear-gradient(var(--g100) 1px, transparent 1px), linear-gradient(90deg, var(--g100) 1px, transparent 1px)';
const card = css.slice(css.indexOf('.main-content > .tab-content.active{'), css.indexOf('}', css.indexOf('.main-content > .tab-content.active{')));
assert.ok(card.includes('background-color:white') && card.includes(tile) && card.includes('background-size:28px 28px'), 'page card is tiled white');
const panel = read('public/css/assistant.css');
// The top-level rule, not the phone override that also names .assistant-main.card.
const chat = (panel.match(/^\.assistant-main\.card \{[^}]*\}/m) || [''])[0];
assert.ok(chat.includes('background-color:white') && chat.includes(tile) && chat.includes('background-size:28px 28px'), 'the same fill as the chat panel');
// The assistant brings a full-bleed layout, so it replaces the shell card // The assistant brings a full-bleed layout, so it replaces the shell card
// rather than nesting a second one inside it. // rather than nesting a second one inside it.
assert.match(css, /\.main-content > #assistant-tab\.active\{padding:0;border:none/); assert.match(css, /\.main-content > #assistant-tab\.active\{padding:0;border:none/);
@ -535,14 +549,15 @@ test('the menu head reads toggle, mark, search — and account tabs are not list
} }
}); });
test('desktop: the assistant rail is the app sidebar, flush and white', () => { test('desktop: the assistant rail is the app sidebar, flush and flat', () => {
const css = read('public/css/assistant.css'); const css = read('public/css/assistant.css');
const styles = read('public/css/styles.css'); const styles = read('public/css/styles.css');
// Inside the content area's 14px padding the rail sat on the tiled ground, its // Inside the content area's 14px padding the rail sat on the tiled ground, its
// name card 6px lower and further in, and the page 28px taller than the window. // name card 6px lower and further in, and the page 28px taller than the window.
const desk = css.slice(css.lastIndexOf('@media (min-width:961px) {')); const desk = css.slice(css.lastIndexOf('@media (min-width:961px) {'));
assert.match(desk, /body\.assistant-workspace \.main-content \{ padding:0; \}/); assert.match(desk, /body\.assistant-workspace \.main-content \{ padding:0; \}/);
assert.match(desk, /body\.assistant-workspace \.assistant-history \{ padding:0; gap:0; background:white; border-right:1\.5px solid var\(--g200\); \}/); assert.match(desk, /body\.assistant-workspace \.assistant-history \{ padding:0; gap:0; \}/,
'flush, and flat on the tiled ground like the workspace sidebar');
assert.match(desk, /body\.assistant-workspace \.assistant-side \{ margin-block:14px; height:auto; \}/, 'the panels carry the 14px'); assert.match(desk, /body\.assistant-workspace \.assistant-side \{ margin-block:14px; height:auto; \}/, 'the panels carry the 14px');
// Same insets as the sidebar: .menu-head, .assistant-mode-switch, .account-card. // Same insets as the sidebar: .menu-head, .assistant-mode-switch, .account-card.
const rail = styles.slice(styles.lastIndexOf('@media (min-width:961px) {')); const rail = styles.slice(styles.lastIndexOf('@media (min-width:961px) {'));