From ea9ffbaea5c475225c23f1dd497498b09014e19d Mon Sep 17 00:00:00 2001 From: hmrserver <20267810+hmrserver@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:03:59 -0700 Subject: [PATCH] Add right-sidebar edge peek (extracted from #7423) Arc-style hover peek: when the right sidebar is closed, hovering the window's right edge reveals it as a floating overlay without reflowing the editor; moving away dismisses it, and an explicit open pins it. Extracted unchanged from PR #7423 so it can be reviewed on its own. Co-authored-by: Orca --- src/renderer/src/App.tsx | 7 +- .../src/components/right-sidebar/index.tsx | 42 +++- .../right-sidebar-edge-peek.test.tsx | 225 ++++++++++++++++++ .../right-sidebar/right-sidebar-edge-peek.tsx | 155 ++++++++++++ src/renderer/src/store/slices/editor.ts | 13 +- 5 files changed, 429 insertions(+), 13 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/right-sidebar-edge-peek.test.tsx create mode 100644 src/renderer/src/components/right-sidebar/right-sidebar-edge-peek.tsx diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index eb34e42b62e..6c2522288d0 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -55,6 +55,7 @@ import { useSystemPrefersDark } from './components/terminal-pane/use-system-prefers-dark' import RightSidebar from './components/right-sidebar' +import { RightSidebarEdgePeekZone } from './components/right-sidebar/right-sidebar-edge-peek' import { StarNagCard } from './components/StarNagCard' import { StarNagAgentValueMomentObserver } from './components/star-nag/StarNagAgentValueMomentObserver' import { StarNagToastHost } from './components/star-nag/StarNagToastHost' @@ -2211,7 +2212,10 @@ function App(): React.JSX.Element { 'The app is still running. Retry the shell or use the menu to report the crash details.' )} > -
+ {/* Why: relative anchors the right-sidebar edge-peek overlay to + this row, so a peek spans the same height as the docked sidebar + and ends above the status bar. */} +
{/* Why: the non-workspace titlebar lives inside this left+center wrapper so it does not span over the right-sidebar column — when the right sidebar is open, its own header anchors at the @@ -2428,6 +2432,7 @@ function App(): React.JSX.Element { Its heavy panels disconnect while closed so workspace wake stays responsive. Unmount on the tasks view since that surface is intentionally distraction-free. */} + {showRightSidebarControls ? : null} {showRightSidebarControls ? ( s.rightSidebarOpen) + const rightSidebarPeek = useAppStore((s) => s.rightSidebarPeek) + const setRightSidebarPeek = useAppStore((s) => s.setRightSidebarPeek) const rightSidebarWidth = useAppStore((s) => s.rightSidebarWidth) const setRightSidebarWidth = useAppStore((s) => s.setRightSidebarWidth) const rightSidebarTab = useAppStore((s) => s.rightSidebarTab) @@ -69,11 +72,17 @@ function RightSidebarInner(): React.JSX.Element { const setRightSidebarTab = useAppStore((s) => s.setRightSidebarTab) const showRightSidebarFiles = useAppStore((s) => s.showRightSidebarFiles) const toggleRightSidebar = useAppStore((s) => s.toggleRightSidebar) - const checksStatus = useAppStore((s) => (s.rightSidebarOpen ? getActiveChecksStatus(s) : null)) + // An edge-hover peek shows the same content as a pinned-open sidebar; only + // the positioning differs (floating overlay vs in-flow panel). + const isPeeking = rightSidebarPeek && !rightSidebarOpen + const sidebarVisible = rightSidebarOpen || isPeeking + const checksStatus = useAppStore((s) => + s.rightSidebarOpen || s.rightSidebarPeek ? getActiveChecksStatus(s) : null + ) const activityBarPosition = useAppStore((s) => s.activityBarPosition) const setActivityBarPosition = useAppStore((s) => s.setActivityBarPosition) const [topActivityStripWidth, setTopActivityStripWidth] = useState(null) - const activeWorktreeId = useAppStore((s) => (rightSidebarOpen ? s.activeWorktreeId : null)) + const activeWorktreeId = useAppStore((s) => (sidebarVisible ? s.activeWorktreeId : null)) // Why: source control and checks are meaningless for non-git folders. // Hide those tabs so the activity bar only shows relevant actions. const activeWorktree = useAppStore((s) => @@ -204,8 +213,8 @@ function RightSidebarInner(): React.JSX.Element { windowWidth, activityBarSideWidth ) - const { containerRef, onResizeStart } = useSidebarResize({ - isOpen: rightSidebarOpen, + const { containerRef, isResizing, onResizeStart } = useSidebarResize({ + isOpen: sidebarVisible, width: renderedRightSidebarWidth, minWidth: RIGHT_SIDEBAR_MIN_WIDTH, maxWidth, @@ -213,9 +222,15 @@ function RightSidebarInner(): React.JSX.Element { renderedExtraWidth: activityBarSideWidth, setWidth: setRightSidebarWidth }) + useRightSidebarEdgePeekDismiss({ + isPeeking, + isResizing, + setPeek: setRightSidebarPeek, + overlayRef: containerRef + }) const topActivityStripRef = useMeasuredWidth(setTopActivityStripWidth) - const panelContent = rightSidebarOpen ? ( + const panelContent = sidebarVisible ? (
{/* Why: sidebar panels no longer use key={activeWorktreeId} because the full unmount/remount cycle on every worktree switch triggered @@ -228,7 +243,7 @@ function RightSidebarInner(): React.JSX.Element { property) rather than in a bottom-docked dashboard panel that competed with file Explorer/Search for vertical space. The right sidebar is back to tab-only content. */} - +
) : null @@ -248,7 +263,9 @@ function RightSidebarInner(): React.JSX.Element { /> )) - const closeButton = rightSidebarOpen ? ( + // During a peek the same button pins the sidebar open (toggle clears the + // peek flag alongside flipping open). + const closeButton = sidebarVisible ? (