@@ -2,16 +2,11 @@ import React from "react";
22import { RIGHT_SIDEBAR_TAB_KEY , RIGHT_SIDEBAR_COLLAPSED_KEY } from "@/common/constants/storage" ;
33import { usePersistedState } from "@/browser/hooks/usePersistedState" ;
44import { useWorkspaceUsage , useWorkspaceStatsSnapshot } from "@/browser/stores/WorkspaceStore" ;
5- import { useProviderOptions } from "@/browser/hooks/useProviderOptions" ;
6- import { useResizeObserver } from "@/browser/hooks/useResizeObserver" ;
75import { useFeatureFlags } from "@/browser/contexts/FeatureFlagsContext" ;
8- import { useAutoCompactionSettings } from "@/browser/hooks/useAutoCompactionSettings" ;
96import { ErrorBoundary } from "./ErrorBoundary" ;
107import { CostsTab } from "./RightSidebar/CostsTab" ;
118import { StatsTab } from "./RightSidebar/StatsTab" ;
12- import { VerticalTokenMeter } from "./RightSidebar/VerticalTokenMeter" ;
139import { ReviewPanel } from "./RightSidebar/CodeReview/ReviewPanel" ;
14- import { calculateTokenMeterData } from "@/common/utils/tokens/tokenMeterUtils" ;
1510import { sumUsageHistory , type ChatUsageDisplay } from "@/common/utils/tokens/usageAggregator" ;
1611import { matchesKeybind , KEYBINDS , formatKeybind } from "@/browser/utils/ui/keybinds" ;
1712import { Tooltip , TooltipTrigger , TooltipContent } from "./ui/tooltip" ;
@@ -34,7 +29,7 @@ function formatTabDuration(ms: number): string {
3429}
3530
3631interface SidebarContainerProps {
37- collapsed : boolean ;
32+ collapsed ? : boolean ;
3833 wide ?: boolean ;
3934 /** Custom width from drag-resize (persisted per-tab by AIView) */
4035 customWidth ?: number ;
@@ -49,7 +44,7 @@ interface SidebarContainerProps {
4944 * SidebarContainer - Main sidebar wrapper with dynamic width
5045 *
5146 * Width priority (first match wins):
52- * 1. collapsed (20px) - Shows vertical token meter only
47+ * 1. collapsed (20px) - Manual collapse via toggle
5348 * 2. customWidth - From drag-resize (persisted per-tab)
5449 * 3. wide - Auto-calculated max width for Review tab (when not drag-resizing)
5550 * 4. default (300px) - Costs tab when no customWidth saved
@@ -64,7 +59,7 @@ const SidebarContainer: React.FC<SidebarContainerProps> = ({
6459 "aria-label" : ariaLabel ,
6560} ) => {
6661 const width = collapsed
67- ? "20px"
62+ ? "32px" // Match left sidebar collapsed width (w-8 = 32px)
6863 : customWidth
6964 ? `${ customWidth } px`
7065 : wide
@@ -76,8 +71,6 @@ const SidebarContainer: React.FC<SidebarContainerProps> = ({
7671 className = { cn (
7772 "bg-sidebar border-l border-border-light flex flex-col overflow-hidden flex-shrink-0" ,
7873 ! isResizing && "transition-[width] duration-200" ,
79- collapsed && "sticky right-0 z-10 shadow-[-2px_0_4px_rgba(0,0,0,0.2)]" ,
80- // Mobile: Show vertical meter when collapsed (20px), full width when expanded
8174 "max-md:border-l-0 max-md:border-t max-md:border-border-light" ,
8275 ! collapsed && "max-md:w-full max-md:relative max-md:max-h-[50vh]"
8376 ) }
@@ -97,7 +90,6 @@ export type { TabType };
9790interface RightSidebarProps {
9891 workspaceId : string ;
9992 workspacePath : string ;
100- chatAreaRef : React . RefObject < HTMLDivElement > ;
10193 /** Custom width in pixels (persisted per-tab, provided by AIView) */
10294 width ?: number ;
10395 /** Drag start handler for resize */
@@ -113,7 +105,6 @@ interface RightSidebarProps {
113105const RightSidebarComponent : React . FC < RightSidebarProps > = ( {
114106 workspaceId,
115107 workspacePath,
116- chatAreaRef,
117108 width,
118109 onStartResize,
119110 isResizing = false ,
@@ -123,6 +114,9 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
123114 // Global tab preference (not per-workspace)
124115 const [ selectedTab , setSelectedTab ] = usePersistedState < TabType > ( RIGHT_SIDEBAR_TAB_KEY , "costs" ) ;
125116
117+ // Manual collapse state (persisted globally)
118+ const [ collapsed , setCollapsed ] = usePersistedState < boolean > ( RIGHT_SIDEBAR_COLLAPSED_KEY , false ) ;
119+
126120 const { statsTabState } = useFeatureFlags ( ) ;
127121 const statsTabEnabled = Boolean ( statsTabState ?. enabled ) ;
128122
@@ -160,10 +154,6 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
160154
161155 const usage = useWorkspaceUsage ( workspaceId ) ;
162156
163- const { options } = useProviderOptions ( ) ;
164- const use1M = options . anthropic ?. use1MContext ?? false ;
165- const chatAreaSize = useResizeObserver ( chatAreaRef ) ;
166-
167157 const baseId = `right-sidebar-${ workspaceId } ` ;
168158 const costsTabId = `${ baseId } -tab-costs` ;
169159 const statsTabId = `${ baseId } -tab-stats` ;
@@ -172,10 +162,6 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
172162 const statsPanelId = `${ baseId } -panel-stats` ;
173163 const reviewPanelId = `${ baseId } -panel-review` ;
174164
175- // Use lastContextUsage for context window display (last step = actual context size)
176- const lastUsage = usage ?. liveUsage ?? usage ?. lastContextUsage ;
177- const model = lastUsage ?. model ?? null ;
178-
179165 // Calculate session cost for tab display
180166 const sessionCost = React . useMemo ( ( ) => {
181167 const parts : ChatUsageDisplay [ ] = [ ] ;
@@ -206,90 +192,17 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
206192 return total > 0 ? total : null ;
207193 } ) ( ) ;
208194
209- // Auto-compaction settings: threshold per-model
210- const { threshold : autoCompactThreshold , setThreshold : setAutoCompactThreshold } =
211- useAutoCompactionSettings ( workspaceId , model ) ;
212-
213- // Memoize vertical meter data calculation to prevent unnecessary re-renders
214- const verticalMeterData = React . useMemo ( ( ) => {
215- return lastUsage
216- ? calculateTokenMeterData ( lastUsage , model ?? "unknown" , use1M , true )
217- : { segments : [ ] , totalTokens : 0 , totalPercentage : 0 } ;
218- } , [ lastUsage , model , use1M ] ) ;
219-
220- // Calculate if we should show collapsed view with hysteresis
221- // Strategy: Observe ChatArea width directly (independent of sidebar width)
222- // - ChatArea has min-width: 750px and flex: 1
223- // - Use hysteresis to prevent oscillation:
224- // * Collapse when chatAreaWidth <= 800px (tight space)
225- // * Expand when chatAreaWidth >= 1100px (lots of space)
226- // * Between 800-1100: maintain current state (dead zone)
227- const COLLAPSE_THRESHOLD = 800 ; // Collapse below this
228- const EXPAND_THRESHOLD = 1100 ; // Expand above this
229- const chatAreaWidth = chatAreaSize ?. width ?? 1000 ; // Default to large to avoid flash
230-
231- // Persist collapsed state globally (not per-workspace) since chat area width is shared
232- // This prevents animation flash when switching workspaces - sidebar maintains its state
233- const [ showCollapsed , setShowCollapsed ] = usePersistedState < boolean > (
234- RIGHT_SIDEBAR_COLLAPSED_KEY ,
235- false
236- ) ;
237-
238- React . useEffect ( ( ) => {
239- // Never collapse when Review tab is active - code review needs space
240- if ( selectedTab === "review" ) {
241- if ( showCollapsed ) {
242- setShowCollapsed ( false ) ;
243- }
244- return ;
245- }
246-
247- // If the sidebar is custom-resized (wider than the default Costs width),
248- // auto-collapse based on chatAreaWidth can oscillate between expanded and
249- // collapsed states (because collapsed is 20px but expanded can be much wider),
250- // which looks like a constant flash. In that case, keep it expanded and let
251- // the user resize manually.
252- if ( width !== undefined && width > 300 ) {
253- if ( showCollapsed ) {
254- setShowCollapsed ( false ) ;
255- }
256- return ;
257- }
258-
259- // Normal hysteresis for Costs/Tools tabs
260- if ( chatAreaWidth <= COLLAPSE_THRESHOLD ) {
261- setShowCollapsed ( true ) ;
262- } else if ( chatAreaWidth >= EXPAND_THRESHOLD ) {
263- setShowCollapsed ( false ) ;
264- }
265- // Between thresholds: maintain current state (no change)
266- } , [ chatAreaWidth , selectedTab , showCollapsed , setShowCollapsed , width ] ) ;
267-
268- // Single render point for VerticalTokenMeter
269- // Shows when: (1) collapsed, OR (2) Review tab is active
270- const showMeter = showCollapsed || selectedTab === "review" ;
271- const autoCompactionProps = React . useMemo (
272- ( ) => ( {
273- threshold : autoCompactThreshold ,
274- setThreshold : setAutoCompactThreshold ,
275- } ) ,
276- [ autoCompactThreshold , setAutoCompactThreshold ]
277- ) ;
278- const verticalMeter = showMeter ? (
279- < VerticalTokenMeter data = { verticalMeterData } autoCompaction = { autoCompactionProps } />
280- ) : null ;
281-
282195 return (
283196 < SidebarContainer
284- collapsed = { showCollapsed }
197+ collapsed = { collapsed }
285198 wide = { selectedTab === "review" && ! width } // Auto-wide only if not drag-resizing
286199 customWidth = { width } // Per-tab resized width from AIView
287200 isResizing = { isResizing }
288201 role = "complementary"
289202 aria-label = "Workspace insights"
290203 >
291204 { /* Full view when not collapsed */ }
292- < div className = { cn ( "flex-row h-full" , ! showCollapsed ? " flex" : "hidden" ) } >
205+ < div className = { cn ( "flex h-full flex-row" , collapsed && "hidden" ) } >
293206 { /* Resize handle (left edge) */ }
294207 { onStartResize && (
295208 < div
@@ -301,11 +214,6 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
301214 />
302215 ) }
303216
304- { /* Render meter when Review tab is active */ }
305- { selectedTab === "review" && (
306- < div className = "bg-sidebar flex w-5 shrink-0 flex-col" > { verticalMeter } </ div >
307- ) }
308-
309217 < div className = "flex min-w-0 flex-1 flex-col" >
310218 < div
311219 className = "border-border-light flex gap-1 border-b px-2 py-1.5"
@@ -442,8 +350,22 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
442350 </ div >
443351 </ div >
444352 </ div >
445- { /* Render meter in collapsed view when sidebar is collapsed */ }
446- < div className = { cn ( "h-full" , showCollapsed ? "flex" : "hidden" ) } > { verticalMeter } </ div >
353+
354+ { /* Collapse/expand toggle at bottom - matches left sidebar pattern */ }
355+ < Tooltip >
356+ < TooltipTrigger asChild >
357+ < button
358+ onClick = { ( ) => setCollapsed ( ! collapsed ) }
359+ aria-label = { collapsed ? "Expand sidebar" : "Collapse sidebar" }
360+ className = "text-muted border-dark hover:bg-hover hover:text-foreground mt-auto flex h-9 w-full cursor-pointer items-center justify-center border-t border-none bg-transparent p-0 text-sm transition-all duration-200"
361+ >
362+ { collapsed ? "«" : "»" }
363+ </ button >
364+ </ TooltipTrigger >
365+ < TooltipContent align = "center" >
366+ { collapsed ? "Expand sidebar" : "Collapse sidebar" }
367+ </ TooltipContent >
368+ </ Tooltip >
447369 </ SidebarContainer >
448370 ) ;
449371} ;
0 commit comments