diff --git a/app/globals.css b/app/globals.css index 4010ec13d4..2fc77b0d34 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1676,6 +1676,48 @@ span.token.table { } } +/* Full-width mode keeps the mounted viewer and saved split width intact. */ +@media (min-width: 641px) { + .right-panel-container.right-panel-open.right-panel-full-width { + position: fixed; + inset: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left); + z-index: 250; + width: auto; + min-width: 0; + height: var(--app-viewport-height, 100dvh); + border-left: none !important; + box-shadow: none; + transform: none; + transition: none; + } + .right-panel-container.right-panel-full-width > * { + width: 100%; + min-width: 0; + } +} +.file-panel-expand-button { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 36px; + height: 36px; + padding: 0; + background: var(--bg-selected); + border: none; + border-left: 1px solid var(--border); + color: var(--text); + cursor: pointer; +} +.file-panel-expand-button:hover, +.file-panel-expand-button[aria-pressed="true"] { + color: var(--accent); +} +.file-panel-expand-button:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -3px; +} + /* Mobile sidebar: slide in/out as overlay */ @media (max-width: 640px) { .chat-stats-center { @@ -1722,6 +1764,7 @@ span.token.table { width: 100%; min-width: 0; } + .file-panel-expand-button, .panel-resize-handle, .right-panel-overlay-backdrop { display: none; diff --git a/components/AppShell.mobile-toolbar.test.mjs b/components/AppShell.mobile-toolbar.test.mjs index e95c32add2..536d70139f 100644 --- a/components/AppShell.mobile-toolbar.test.mjs +++ b/components/AppShell.mobile-toolbar.test.mjs @@ -99,3 +99,12 @@ test("places trust warnings below the mobile toolbar and the file toggle in tool assert.doesNotMatch(source, /File panel toggle — always visible at top-right/); assert.doesNotMatch(source, /position: "fixed", top: "env\(safe-area-inset-top\)"/); }); + +test("closes top-bar dropdowns when the file panel expands to full width", () => { + assert.match( + source, + /const handleRightPanelExpandToggle = useCallback\(\(\) => \{\s*setActiveTopPanel\(null\);\s*setRightPanelExpanded\(\(expanded\) => !expanded\);/, + ); + assert.match(source, /onClick=\{handleRightPanelExpandToggle\}/); + assert.match(source, /if \(rightPanelFullWidth\) setActiveTopPanel\(null\);/); +}); diff --git a/components/AppShell.tsx b/components/AppShell.tsx index 86a1f4d426..514dbadb93 100644 --- a/components/AppShell.tsx +++ b/components/AppShell.tsx @@ -172,6 +172,11 @@ export function AppShell() { const [projectTrustError, setProjectTrustError] = useState(null); const [sidebarOpen, setSidebarOpen] = useState(true); const [rightPanelOpen, setRightPanelOpen] = useState(false); + const [rightPanelExpanded, setRightPanelExpanded] = useState(false); + const rightPanelFullWidth = rightPanelOpen && rightPanelExpanded && !isMobile; + useEffect(() => { + if (!rightPanelOpen || isMobile) setRightPanelExpanded(false); + }, [rightPanelOpen, isMobile]); const [mobileToolbarMoreOpen, setMobileToolbarMoreOpen] = useState(false); const [mobileSidebarReady, setMobileSidebarReady] = useState(false); const sidebarWidthRef = useRef(SIDEBAR_DEFAULT_WIDTH); @@ -334,6 +339,10 @@ export function AppShell() { } }, [hasSubagentSessions]); + useEffect(() => { + if (rightPanelFullWidth) setActiveTopPanel(null); + }, [rightPanelFullWidth]); + const toggleTopPanel = useCallback(( panel: "agents" | "branches" | "system" | "tools" | "session" | "language" | "theme", keepMobileToolbarOpen = false, @@ -393,6 +402,11 @@ export function AppShell() { setRightPanelOpen((open) => !open); }, [isMobile]); + const handleRightPanelExpandToggle = useCallback(() => { + setActiveTopPanel(null); + setRightPanelExpanded((expanded) => !expanded); + }, []); + useEffect(() => { if (!mobileToolbarMoreOpen) return; @@ -1982,6 +1996,7 @@ export function AppShell() {
+
{/* Top bar with sidebar toggle */}
@@ -2558,6 +2574,7 @@ export function AppShell() { {rightPanelOpen && (
+