From b1964c217c9fc0f55eeb4f5c5f2983ae1dfd53eb Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 16 Jan 2026 00:26:25 +0200 Subject: [PATCH] feat: enhance VSCode layout with expanded view and responsive design; update sidebar visibility and settings for VSCode runtime --- CHANGELOG.md | 7 +- .../ui/src/components/layout/VSCodeLayout.tsx | 87 +++++++++++++++---- .../openchamber/OpenChamberSidebar.tsx | 10 ++- .../openchamber/OpenChamberVisualSettings.tsx | 2 +- .../ui/src/components/views/SettingsView.tsx | 16 ++-- packages/ui/src/hooks/useKeyboardShortcuts.ts | 5 +- packages/vscode/CHANGELOG.md | 12 +++ packages/vscode/package.json | 19 +++- .../vscode/src/SessionEditorPanelProvider.ts | 22 +++-- packages/vscode/src/extension.ts | 16 ++++ 10 files changed, 161 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96aef17f..b7dc3f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -- Git Identities: added “default identity” setting with a one-click set/unset; automatically applies default identity in Git view for repos without a local identity, and uses it as the preferred identity for skills catalog auth flows. +- UI: added a new Files tab to browse workspace files directly from the interface. +- Diff: enhanced the diff viewer with mobile support and the ability to ask the agent for comments on changes. +- Git Identities: added "default identity" setting with one-click set/unset and automatic local identity detection. +- VSCode: improved server management to ensure it initializes within the workspace directory with context-aware readiness checks. +- VSCode: added responsive layout with sessions sidebar + chat side-by-side when wide, compact header, and streamlined settings. +- Web: the server now automatically resolves and uses an available port if the default is occupied. ## [1.4.9] - 2026-01-14 diff --git a/packages/ui/src/components/layout/VSCodeLayout.tsx b/packages/ui/src/components/layout/VSCodeLayout.tsx index 0b9c11fe..c2010677 100644 --- a/packages/ui/src/components/layout/VSCodeLayout.tsx +++ b/packages/ui/src/components/layout/VSCodeLayout.tsx @@ -11,6 +11,10 @@ import { RiAddLine, RiArrowLeftLine, RiRobot2Line, RiSettings3Line } from '@remi // Width threshold for mobile vs desktop layout in settings const MOBILE_WIDTH_THRESHOLD = 550; +// Width threshold for expanded layout (sidebar + chat side by side) +const EXPANDED_LAYOUT_THRESHOLD = 700; +// Sessions sidebar width in expanded layout +const SESSIONS_SIDEBAR_WIDTH = 280; type VSCodeView = 'sessions' | 'chat' | 'settings'; @@ -38,7 +42,7 @@ export const VSCodeLayout: React.FC = () => { const hasAppliedInitialSession = React.useRef(false); - const [currentView, setCurrentView] = React.useState('chat'); + const [currentView, setCurrentView] = React.useState('sessions'); const [containerWidth, setContainerWidth] = React.useState(0); const containerRef = React.useRef(null); const currentSessionId = useSessionStore((state) => state.currentSessionId); @@ -83,16 +87,16 @@ export const VSCodeLayout: React.FC = () => { void vscodeApi.executeCommand('openchamber.setActiveSession', currentSessionId, activeSessionTitle); }, [activeSessionTitle, currentSessionId, runtimeApis.vscode]); - // If the active session disappears (e.g., deleted), show a new chat view + // If the active session disappears (e.g., deleted), go back to sessions list React.useEffect(() => { if (viewMode === 'editor') { return; } - if (!currentSessionId && !newSessionDraftOpen) { - openNewSessionDraft(); + if (!currentSessionId && !newSessionDraftOpen && currentView === 'chat') { + setCurrentView('sessions'); } - }, [currentSessionId, newSessionDraftOpen, openNewSessionDraft, viewMode]); + }, [currentSessionId, newSessionDraftOpen, currentView, viewMode]); const handleBackToSessions = React.useCallback(() => { setCurrentView('sessions'); @@ -199,19 +203,24 @@ export const VSCodeLayout: React.FC = () => { if (hasAppliedInitialSession.current) { return; } - if (!initialSessionId) { - return; - } if (!hasInitializedOnce || connectionStatus !== 'connected') { return; } + + // No initialSessionId means open a new session draft + if (!initialSessionId) { + hasAppliedInitialSession.current = true; + openNewSessionDraft(); + return; + } + if (!sessions.some((session) => session.id === initialSessionId)) { return; } hasAppliedInitialSession.current = true; void useSessionStore.getState().setCurrentSession(initialSessionId); - }, [connectionStatus, hasInitializedOnce, initialSessionId, sessions, viewMode]); + }, [connectionStatus, hasInitializedOnce, initialSessionId, openNewSessionDraft, sessions, viewMode]); // Hydrate messages when viewing chat React.useEffect(() => { @@ -254,10 +263,20 @@ export const VSCodeLayout: React.FC = () => { }, []); const usesMobileLayout = containerWidth > 0 && containerWidth < MOBILE_WIDTH_THRESHOLD; + const usesExpandedLayout = containerWidth >= EXPANDED_LAYOUT_THRESHOLD; + + // In expanded layout, always show chat (with sidebar alongside) + // Navigate to chat automatically when expanded layout is enabled and we're on sessions view + React.useEffect(() => { + if (usesExpandedLayout && currentView === 'sessions' && viewMode === 'sidebar') { + setCurrentView('chat'); + } + }, [usesExpandedLayout, currentView, viewMode]); return (
{viewMode === 'editor' ? ( + // Editor mode: just chat, no sidebar
session.id === currentSessionId)?.title || 'Chat'} @@ -270,7 +289,45 @@ export const VSCodeLayout: React.FC = () => {
+ ) : currentView === 'settings' ? ( + // Settings view + setCurrentView(usesExpandedLayout ? 'chat' : 'sessions')} + forceMobile={usesMobileLayout} + /> + ) : usesExpandedLayout ? ( + // Expanded layout: sessions sidebar + chat side by side +
+ {/* Sessions sidebar */} +
+ +
+ {/* Chat content */} +
+ session.id === currentSessionId)?.title || 'Chat'} + showMcp + showContextUsage + /> +
+ + + +
+
+
) : currentView === 'sessions' ? ( + // Compact layout: sessions list (drill-down)
{ />
- ) : currentView === 'settings' ? ( - setCurrentView('sessions')} - forceMobile={usesMobileLayout} - /> ) : ( + // Compact layout: chat view (drill-down)
= ({ title, showBack, onBack, on const contextUsage = getContextUsage(contextLimit, outputLimit); return ( -
+
{showBack && onBack && ( )} -

{title}

+

{title}

{onNewSession && (
)} - {shouldShow('diffLayout') && !isMobile && ( + {shouldShow('diffLayout') && !isMobile && !isVSCodeRuntime() && (

diff --git a/packages/ui/src/components/views/SettingsView.tsx b/packages/ui/src/components/views/SettingsView.tsx index 685ce258..3439f54b 100644 --- a/packages/ui/src/components/views/SettingsView.tsx +++ b/packages/ui/src/components/views/SettingsView.tsx @@ -32,12 +32,16 @@ import { ErrorBoundary } from '@/components/ui/ErrorBoundary'; import { useDeviceInfo } from '@/lib/device'; import { isVSCodeRuntime } from '@/lib/desktop'; -const SETTINGS_SECTIONS = (() => { - const filtered = SIDEBAR_SECTIONS.filter(section => section.id !== 'sessions'); +const getSettingsSections = (isVSCode: boolean) => { + let filtered = SIDEBAR_SECTIONS.filter(section => section.id !== 'sessions'); + // Hide Git Identities tab for VS Code + if (isVSCode) { + filtered = filtered.filter(section => section.id !== 'git-identities'); + } const settingsSection = filtered.find(s => s.id === 'settings'); const otherSections = filtered.filter(s => s.id !== 'settings'); return settingsSection ? [settingsSection, ...otherSections] : filtered; -})(); +}; // Same constraints as main sidebar const SETTINGS_SIDEBAR_MIN_WIDTH = 200; @@ -89,6 +93,8 @@ export const SettingsView: React.FC = ({ onClose, forceMobile const isVSCode = React.useMemo(() => isVSCodeRuntime(), []); + const settingsSections = React.useMemo(() => getSettingsSections(isVSCode), [isVSCode]); + React.useEffect(() => { if (typeof window === 'undefined') return; setIsDesktopApp(typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'); @@ -143,7 +149,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile return formatProjectLabel(rawLabel); }, [activeProject, formatProjectLabel]); - const showProjectSwitcher = sortedProjects.length > 0; + const showProjectSwitcher = sortedProjects.length > 0 && !isVSCode; const showTabLabels = containerWidth === 0 || containerWidth >= TAB_LABELS_MIN_WIDTH; @@ -352,7 +358,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
{/* Leading divider before first tab - only on Mac desktop */} {!isMobile && showLeadingDivider &&