merge(dev): integrate latest upstream dev (PR #6840 sidebar animations)

- Merge upstream/dev at 0116f0a62b
- Incorporate upstream sidebar toggle animation and auto-collapse measurement fixes
- Preserve upstream CSS snapshot baseline in tests/css_snapshot/baseline.json
- Retain all PR #6741 photo-editor features, security controls, and mobile fixes
This commit is contained in:
Alexandre Teixeira
2026-10-28 19:30:57 +00:00
3 changed files with 106 additions and 65 deletions
+33 -7
View File
@@ -315,7 +315,7 @@ body.bg-pattern-ascii-fireflies {
width: 240px;
background: var(--sidebar-bg, var(--panel));
border-right: 1px solid var(--border);
transition: width 0.25s ease, opacity 0.2s ease, padding 0.25s ease;
transition: width 0.25s ease, opacity 0.2s ease, padding 0.25s ease, visibility 0.25s;
display: flex;
flex-direction: column;
flex-shrink: 0;
@@ -361,12 +361,23 @@ body.bg-pattern-ascii-fireflies {
/* !important so it beats the inline width init.js restores from storage —
otherwise the width never changes and only opacity animates, making the
collapse look instant. With this, width animates from the inline value
down to 0 via the .sidebar width transition. */
down to 0 via the .sidebar width transition.
display overrides the global .hidden utility, which would otherwise
remove the box before any transition can run. visibility (transitioned
on .sidebar) flips to hidden only once the collapse has finished,
taking the closed sidebar out of the tab order. */
display: flex;
width: 0 !important;
padding: 0 !important;
border: none;
overflow: hidden;
opacity: 0;
visibility: hidden;
pointer-events: none;
}
/* IDs so this also beats the mobile drawer's !important transition. */
@media (prefers-reduced-motion: reduce) {
#sidebar, #icon-rail { transition: none !important; }
}
/* ===== Sidebar User Bar ===== */
.sidebar-user-bar {
@@ -449,10 +460,6 @@ body.bg-pattern-ascii-fireflies {
}
.sidebar.hidden .sidebar-user-bar {
display: none;
}
/* Sticky sidebar header — logo, never scrolls */
.sidebar-header {
display: flex;
@@ -673,6 +680,24 @@ body.bg-pattern-ascii-fireflies {
margin-bottom: calc(-1 * var(--safe-bottom));
padding-bottom: calc(8px + var(--safe-bottom));
}
/* Tuck the rail under the sidebar (which paints above it) and slide it
out as the sidebar collapses, so the combined width eases instead of
jumping by the rail's 48px when the two swap. */
.icon-rail,
.icon-rail.right-side {
margin-inline-end: -48px;
transition: margin-inline-end 0.25s ease, display 0.25s allow-discrete;
}
body.sidebar-collapsed:not(.hamburger-only) .icon-rail,
html.ody-sidebar-mini .icon-rail {
margin-inline-end: 0;
}
@starting-style {
body.sidebar-collapsed:not(.hamburger-only) .icon-rail,
html.ody-sidebar-mini .icon-rail {
margin-inline-end: -48px;
}
}
}
.icon-rail-divider {
width: 24px;
@@ -5116,7 +5141,7 @@ body.bg-pattern-ascii-fireflies {
width: 80% !important;
max-width: 340px;
box-shadow: 4px 0 20px rgba(0,0,0,0.5);
transition: transform 0.25s ease, opacity 0.25s ease !important;
transition: transform 0.25s ease, opacity 0.25s ease, visibility 0.25s !important;
opacity: 1 !important;
overflow: visible !important;
/* Force opaque background — Firefox mobile renders the
@@ -5147,6 +5172,7 @@ body.bg-pattern-ascii-fireflies {
}
html.ody-sidebar-off .sidebar,
html.ody-mobile-startup-sidebar-hidden .sidebar {
width: 80% !important;
transform: translateX(-100%) !important;
pointer-events: none !important;
opacity: 0 !important;
+18 -3
View File
@@ -170,6 +170,15 @@ export function initSidebarLayout(Storage, opts) {
syncRailSide();
}
// Move the closed drawer to the other edge without transitioning its
// transform across the screen, so it then slides in from the edge it opens on.
function _setSidebarSideInstantly(sidebar, right) {
sidebar.style.setProperty('transition', 'none', 'important');
sidebar.classList.toggle('right-side', right);
void sidebar.offsetWidth;
sidebar.style.removeProperty('transition');
}
// Deliberate "open the sidebar" used by the mobile swipe gesture (wired at
// module scope). It MUST set _userToggledSidebar so the auto-collapse
// MutationObserver doesn't immediately re-hide it (the swipe was opening it,
@@ -189,7 +198,7 @@ export function initSidebarLayout(Storage, opts) {
if (side === 'left' || side === 'right') {
const wantRight = side === 'right';
if (sidebar.classList.contains('right-side') !== wantRight) {
sidebar.classList.toggle('right-side', wantRight);
_setSidebarSideInstantly(sidebar, wantRight);
// Swipe-selected sides are temporary on compact/mobile layouts. Do
// not overwrite the user's desktop sidebar preference.
if (window.innerWidth >= 768) {
@@ -227,7 +236,7 @@ export function initSidebarLayout(Storage, opts) {
// Mobile: the hamburger always opens the sidebar from the RIGHT.
// (Not persisted — keeps the desktop side preference untouched.)
if (!sidebar.classList.contains('right-side')) {
sidebar.classList.add('right-side');
_setSidebarSideInstantly(sidebar, true);
if (documentModule && documentModule.swapSide) { try { documentModule.swapSide(); } catch (_) {} }
}
// Opening sidebar — blur keyboard first, then open after layout settles
@@ -312,14 +321,20 @@ export function initSidebarLayout(Storage, opts) {
_wasAutoCollapsed = true;
syncRailSide();
} else if (window.innerWidth >= AUTO_COLLAPSE_WIDTH && isHidden && _wasAutoCollapsed) {
// Only restore if chat won't be too narrow
// Only restore if chat won't be too narrow. Suppress the width
// transition and sync the mode classes (ody-sidebar-mini also forces
// width 0) so the reflow measures the sidebar at its full width.
sidebar.style.transition = 'none';
sidebar.classList.remove('hidden');
syncRailSide();
void document.body.offsetWidth; // reflow
if (chatContainer && chatContainer.offsetWidth < MIN_CHAT_WIDTH) {
sidebar.classList.add('hidden');
} else {
_wasAutoCollapsed = false;
}
void document.body.offsetWidth;
sidebar.style.transition = '';
syncRailSide();
}
}