Self-review recheck against current TopSearchBar.js (L211-222) found that the UserMenu dropdown uses var(--elevation-pronounced), not var(--elevation-ambient) as Brief 3's initial sample showed. Brief 3 now documents the verbatim shape (pronounced + role="menu" + aria-label + overflow-hidden) and the convoy's risk-list note is updated to reflect all three popovers' verbatim chains. No semantic change to the plan; just an accuracy fix the implementer of Brief 3 needs to ship the right code on the first try. Co-authored-by: Cursor <cursoragent@cursor.com>
9.1 KiB
| convoy | brief_number | depends_on | files | ||||
|---|---|---|---|---|---|---|---|
| unify-glass-panel-surfaces | 3 |
|
|
Brief 3: Migrate floating popovers to .glass-panel-strong
Goal (1 sentence)
Migrate three floating surfaces — the sidebar profile dropdown, the mobile drawer, and the TopSearchBar <UserMenu> dropdown — from inline var(--glass-surface-*) + backdropFilter styles to the canonical .glass-panel-strong className, while preserving their existing box-shadow chains (ember rim for the profile dropdown; pronounced elevation for the drawer) via inline override.
Files in scope (do not edit anything else)
components/Layout.js- Sidebar profile dropdown panel (currently around L88-96 inside
<UserProfileDropdown>). - Mobile drawer slide-in panel (currently around L698-709 — the
<div>that takes${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}and thestyle={{ background: 'var(--glass-surface-mid)', backdropFilter: '...', ... }}block). - Do NOT touch the sidebar nav-chip block (around L858-861); that block intentionally uses full-intensity chrome corner lights and stays handrolled — Decision 4 of this convoy's Architecture allowlists it explicitly.
- Sidebar profile dropdown panel (currently around L88-96 inside
components/ui/TopSearchBar.js<UserMenu>dropdown panel (around L214-222 — the absolutely-positioned<div>with thebackground: 'var(--glass-surface-high)'+backdropFilterblock).- Do NOT touch the TopSearchBar
<header>block itself; that block also intentionally uses full-intensity chrome corner lights and is allowlisted.
test/components/Layout.test.js- Add regression-lock assertions that the sidebar profile dropdown and mobile drawer render with
.glass-panel-strongin their className.
- Add regression-lock assertions that the sidebar profile dropdown and mobile drawer render with
Out of scope: the rest of Layout.js (<DesktopSidebar>, <MobileNavigation>, <UserProfileDropdown> trigger button, <DailyEmberWidget>, etc.), the rest of TopSearchBar.js (search input, <CommandPaletteModal> link, etc.), and any deeper menu-item styling (e.g. hover:bg-gray-50 dark:hover:bg-gray-700 cleanup — that's owned by PR #117 / design-sweep-pass).
Conventions to follow
-
Verbatim replacement for each floating-surface
<div>:A. Sidebar profile dropdown panel (Layout.js)
// Before: <div className="absolute bottom-full left-0 right-0 mb-2 rounded-xl z-20" style={{ background: 'var(--glass-surface-high)', backdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))', WebkitBackdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))', boxShadow: 'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient)', }} > // After: <div className="glass-panel-strong absolute bottom-full left-0 right-0 mb-2 rounded-xl z-20" style={{ boxShadow: 'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient)', }} >The
background+backdropFilter+WebkitBackdropFilterinline styles are removed (the class provides them). TheboxShadowis preserved as an inline override because.glass-panel-strongships onlyvar(--rim-light-inner), var(--elevation-ambient)— we need thevar(--ember-rim-subtle)middle entry to keep the ember emphasis the dropdown is known for.B. Mobile drawer panel (Layout.js)
// Before: <div className={` md:hidden fixed inset-y-0 left-0 z-50 w-64 transform transition-transform duration-300 ease-in-out ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} `} style={{ background: 'var(--glass-surface-mid)', backdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))', WebkitBackdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))', boxShadow: 'var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced)', }} > // After: <div className={` glass-panel-strong md:hidden fixed inset-y-0 left-0 z-50 w-64 transform transition-transform duration-300 ease-in-out ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} `} style={{ boxShadow: 'var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced)', }} >Same pattern.
boxShadowis preserved as an inline override so the drawer keeps itsvar(--elevation-pronounced)(heavier) elevationvar(--rim-light-outer)(which.glass-panel-strongdoesn't ship by default).
C. TopSearchBar
<UserMenu>dropdown panel// Before — the absolutely-positioned <div> nested inside UserMenu, // around L211-222 of TopSearchBar.js (verified against current file // 2026-06-04 during architect boot-the-brief recheck): <div role="menu" aria-label="Account menu" className="absolute right-0 top-full mt-2 w-56 rounded-xl z-30 overflow-hidden" style={{ background: 'var(--glass-surface-high)', backdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))', WebkitBackdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))', boxShadow: 'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-pronounced)', }} > // After: <div role="menu" aria-label="Account menu" className="glass-panel-strong absolute right-0 top-full mt-2 w-56 rounded-xl z-30 overflow-hidden" style={{ boxShadow: 'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-pronounced)', }} >Note: this dropdown uses
--elevation-pronounced(not-ambient) — it's a heavier shadow than the sidebar profile dropdown's because it floats higher in the viewport over more content. Preserve that verbatim; do not normalize to-ambient. Therole="menu"+aria-label="Account menu"+overflow-hiddenattributes also stay byte-identical. -
Preserve all children verbatim. Menu items, dividers, icons, click handlers — all stay byte-identical.
-
Do not touch the existing
hover:bg-gray-50 dark:hover:bg-gray-700classes on individual menu items. PR #117 (design-sweep-pass) is already migrating those tonav-item-hover; this brief MUST NOT duplicate that work or it'll merge-conflict. If PR #117 has already merged when this brief dispatches, the relevant lines may already readnav-item-hover— that's fine; just leave them alone. -
Test additions in
test/components/Layout.test.js:- One assertion for the sidebar profile dropdown: render
<Layout user={mockUser} />, simulate clicking the user-profile trigger to open the dropdown, assert the dropdown panelclassNamecontains'glass-panel-strong'. - One assertion for the mobile drawer: render
<Layout user={mockUser} />, simulate opening the drawer (today the test file already has a way to setisMobileMenuOpen; if not, render with the relevant prop / state directly), assert the drawer panelclassNamecontains'glass-panel-strong'. - Use the existing test file's patterns (jsdom env,
cleanup()afterEach,render+screenfrom@testing-library/react). Do NOT add new test dependencies. Do NOT lower any existing assertion in the file.
- One assertion for the sidebar profile dropdown: render
Acceptance criteria
- Sidebar profile dropdown panel uses
className="glass-panel-strong ..."and carries an inlineboxShadowwithvar(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient). - Mobile drawer panel uses
className="glass-panel-strong ..."and carries an inlineboxShadowwithvar(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced). - TopSearchBar
<UserMenu>dropdown panel usesclassName="glass-panel-strong ..."and carries an inlineboxShadowwithvar(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-pronounced). - No inline
background: 'var(--glass-surface-*)'orbackdropFilterstyle remains on any of the three migrated<div>s. - Sidebar nav-chip block (Layout.js ~L858-861) UNCHANGED. TopSearchBar
<header>block UNCHANGED. (Architect's allowlist for chrome-tier surfaces.) test/components/Layout.test.jshas at least 2 new assertions (sidebar dropdown + mobile drawer carryglass-panel-strong).npm run lint+npm run test:run(23+/23+, post Brief 1) pass.- Manual: open sidebar profile dropdown, open mobile drawer, open TopSearchBar UserMenu dropdown in light + dark mode — all three render with visible corner catch-lights and the appropriate shadow elevation.
- No edits to files outside the 3 listed in
files:above.
Rationale (≤3 sentences)
These three popovers are the most visible floating surfaces in the authenticated app and they predate the gradient-border system; migrating them is the largest visual-correctness win of the convoy after Brief 1. Preserving the existing box-shadow chains via inline override (rather than letting them snap to the class default) is non-negotiable — the var(--ember-rim-subtle) ember emphasis on the profile menu and the var(--elevation-pronounced) weight on the drawer are deliberate design choices that must survive the class swap.