fix(ui): keep settings sidebar open, center content, and fix worktree refresh (#1058)

* fix(ui): keep settings sidebar open, center content, and fix worktree refresh

- Prevent settings nav from collapsing on split pages (Magic Prompts, Agents, etc.)

- Center settings content with max-w-3xl for better layout on wide screens

- Widen settings window from 960px to 1200px max-width

- Fix source branch dropdown overflow with max-height and scrolling

- Add worktreeRefreshNonce to trigger immediate sidebar refresh after worktree creation

* ref(ui): address Greptile review — remove dead isNavCollapsed code and split worktree effect

- Remove isNavCollapsed variable and all dead branches (resize handle, renderSettingsNav param, SETTINGS_NAV_RAIL_WIDTH)

- Split SessionSidebar effect so worktreeRefreshNonce only triggers discoverWorktrees, not refreshGlobalSessions

- Both type-check and lint pass

* fix(ui): refine settings and worktree dropdown behavior

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Shyamalan Kannan
2026-04-28 14:52:57 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 4f51abddf4
commit 6ab363f8df
4 changed files with 35 additions and 48 deletions
@@ -1593,7 +1593,7 @@ export function NewWorktreeDialog({
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" sideOffset={6} className="w-[320px] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={existingBranchDropdownContentRef}>
<DropdownMenuContent align="start" sideOffset={6} portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={existingBranchDropdownContentRef}>
<Command shouldFilter={false}>
<CommandInput
placeholder={t('session.newWorktree.searchBranches')}
@@ -1833,7 +1833,7 @@ export function NewWorktreeDialog({
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-[320px] p-0" ref={sourceBranchDropdownContentRef}>
<DropdownMenuContent align="start" portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={sourceBranchDropdownContentRef}>
<Command shouldFilter={false}>
<CommandInput
placeholder={t('session.newWorktree.searchBranches')}
@@ -93,6 +93,7 @@ type ContentProps = {
align?: "start" | "center" | "end";
side?: "top" | "right" | "bottom" | "left";
alignOffset?: number;
portalToBody?: boolean;
style?: React.CSSProperties;
className?: string;
children?: React.ReactNode;
@@ -105,6 +106,7 @@ function DropdownMenuContent({
align,
side,
alignOffset,
portalToBody = false,
style,
children,
onCloseAutoFocus,
@@ -114,7 +116,7 @@ function DropdownMenuContent({
void onCloseAutoFocus
return (
<BaseMenu.Portal container={portalContext?.portalContainer || undefined}>
<BaseMenu.Portal container={portalToBody ? undefined : portalContext?.portalContainer || undefined}>
<BaseMenu.Positioner
sideOffset={sideOffset}
align={align}
@@ -71,7 +71,6 @@ import {
// Same constraints as main sidebar
const SETTINGS_NAV_MIN_WIDTH = 176;
const SETTINGS_NAV_MAX_WIDTH = 280;
const SETTINGS_NAV_RAIL_WIDTH = 48;
type MobileStage = 'nav' | 'page-sidebar' | 'page-content';
@@ -372,8 +371,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
return getSettingsPageMeta(settingsSlug);
}, [settingsSlug]);
// Collapse main nav to icon rail when active page has its own sidebar
const isNavCollapsed = !isMobile && activePageMeta?.kind === 'split';
// Nav is always open (collapsed state removed)
const openChamberSectionBySlug: Partial<Record<SettingsPageSlug, OpenChamberSection>> = React.useMemo(() => ({
appearance: 'visual',
@@ -544,7 +542,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
setMobileStage('page-sidebar');
}, []);
const renderSettingsNav = (collapsed: boolean) => {
const renderSettingsNav = () => {
return (
<div className="flex h-full flex-col overflow-hidden">
{/* Scrollable nav items */}
@@ -556,7 +554,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
if (!Icon) return null;
return (
<Tooltip key={page.slug} delayDuration={collapsed ? 100 : 600}>
<Tooltip key={page.slug} delayDuration={600}>
<TooltipTrigger asChild>
<button
type="button"
@@ -569,12 +567,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
)}
>
<Icon className="h-4 w-4 shrink-0" />
<span
className={cn(
'flex items-center gap-1.5 whitespace-nowrap overflow-hidden transition-opacity duration-150',
collapsed ? 'opacity-0' : 'opacity-100'
)}
>
<span className="flex items-center gap-1.5 whitespace-nowrap overflow-hidden transition-opacity duration-150 opacity-100">
<span className="typography-ui-label font-normal truncate">{getPageTitle(page.slug)}</span>
{(page.slug === 'voice' || page.slug === 'tunnel') && (
<span className="shrink-0 typography-micro px-1 rounded leading-none pb-px text-[var(--status-warning)] bg-[var(--status-warning)]/10">
@@ -584,24 +577,14 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
</span>
</button>
</TooltipTrigger>
{collapsed && (
<TooltipContent side="right" sideOffset={8}>
{getPageTitle(page.slug)}
</TooltipContent>
)}
</Tooltip>
);
})}
</div>
</div>
{/* Footer — hidden when collapsed via overflow on parent */}
<div
className={cn(
'overflow-hidden transition-opacity duration-150',
collapsed ? 'opacity-0 pointer-events-none' : 'opacity-100'
)}
>
{/* Footer */}
<div className="overflow-hidden transition-opacity duration-150 opacity-100">
<div className="border-t border-border bg-sidebar px-2 py-1 space-y-0.5">
{!runtimeCtx.isVSCode && (
<Tooltip delayDuration={300}>
@@ -636,7 +619,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
return (
<div className={cn('flex-1 min-h-0 overflow-hidden', runtimeCtx.isVSCode ? 'bg-background' : 'bg-sidebar')}>
<div className="flex h-full min-h-0 flex-col">
<ErrorBoundary>{renderSettingsNav(false)}</ErrorBoundary>
<ErrorBoundary>{renderSettingsNav()}</ErrorBoundary>
</div>
</div>
);
@@ -686,16 +669,20 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
<div className={cn('w-[264px] min-w-[264px] border-r', runtimeCtx.isVSCode ? 'bg-background' : 'bg-sidebar')} style={{ borderColor: 'var(--interactive-border)' }}>
<ErrorBoundary>{renderPageSidebar(settingsSlug, {})}</ErrorBoundary>
</div>
<div className="flex-1 overflow-hidden bg-background">
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
<div className="flex-1 overflow-auto scrollbar-none bg-background">
<div className="mx-auto w-full max-w-3xl">
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
</div>
</div>
</div>
);
}
return (
<div className="h-full overflow-hidden bg-background">
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
<div className="h-full overflow-auto scrollbar-none bg-background">
<div className="mx-auto w-full max-w-3xl">
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
</div>
</div>
);
};
@@ -792,28 +779,26 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
: runtimeCtx.isVSCode
? 'bg-background'
: 'bg-sidebar',
isResizing && !isNavCollapsed ? '' : 'transition-[width,min-width] duration-200 ease-[cubic-bezier(0.25,0.1,0.25,1)]'
isResizing ? '' : 'transition-[width,min-width] duration-200 ease-[cubic-bezier(0.25,0.1,0.25,1)]'
)}
style={{
width: isNavCollapsed ? `${SETTINGS_NAV_RAIL_WIDTH}px` : `${navWidth}px`,
minWidth: isNavCollapsed ? `${SETTINGS_NAV_RAIL_WIDTH}px` : `${navWidth}px`,
width: `${navWidth}px`,
minWidth: `${navWidth}px`,
borderColor: 'var(--interactive-border)',
}}
>
{!isNavCollapsed && (
<div
className={cn(
'absolute right-0 top-0 z-20 h-full w-[6px] -mr-[3px] cursor-col-resize',
isResizing ? 'bg-primary/30' : 'bg-transparent hover:bg-primary/20'
)}
onPointerDown={handlePointerDown}
role="separator"
aria-orientation="vertical"
aria-label={t('settings.view.actions.resizeNavigation')}
/>
)}
<div
className={cn(
'absolute right-0 top-0 z-20 h-full w-[6px] -mr-[3px] cursor-col-resize',
isResizing ? 'bg-primary/30' : 'bg-transparent hover:bg-primary/20'
)}
onPointerDown={handlePointerDown}
role="separator"
aria-orientation="vertical"
aria-label={t('settings.view.actions.resizeNavigation')}
/>
<ErrorBoundary>
{renderSettingsNav(isNavCollapsed)}
{renderSettingsNav()}
</ErrorBoundary>
</div>
@@ -41,7 +41,7 @@ export const SettingsWindow: React.FC<SettingsWindowProps> = ({ open, onOpenChan
aria-describedby={descriptionId}
className={cn(
'fixed z-50 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]',
'w-[90vw] max-w-[960px] h-[85vh] max-h-[900px]',
'w-[90vw] max-w-[1200px] h-[85vh] max-h-[900px]',
'rounded-xl border shadow-none overflow-hidden',
'bg-background'
)}