diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 250a3a8d..6c8d7949 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -222,6 +222,7 @@ function App({ apis }: AppProps) { const [initRetryExhausted, setInitRetryExhausted] = React.useState(false); const [initRetryEpoch, setInitRetryEpoch] = React.useState(0); const [manualInitRetrying, setManualInitRetrying] = React.useState(false); + const wideChatLayoutEnabled = useUIStore((state) => state.wideChatLayoutEnabled); const isDesktopRuntime = React.useMemo(() => isDesktopShell(), []); const setPlanModeEnabled = useFeatureFlagsStore((state) => state.setPlanModeEnabled); const [bootInjectionStatus, setBootInjectionStatus] = React.useState(() => { @@ -250,6 +251,13 @@ function App({ apis }: AppProps) { setIsVSCodeRuntime(apis.runtime.isVSCode); }, [apis.runtime.isVSCode]); + React.useEffect(() => { + document.documentElement.classList.toggle('wide-chat-layout', wideChatLayoutEnabled); + return () => { + document.documentElement.classList.remove('wide-chat-layout'); + }; + }, [wideChatLayoutEnabled]); + React.useEffect(() => { registerRuntimeAPIs(apis); return () => registerRuntimeAPIs(null); diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx index ff09e3c9..c3554787 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx @@ -128,7 +128,7 @@ const VisualSectionContent: React.FC = () => { // Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Queue mode, Persist draft const ChatSectionContent: React.FC = () => { - return ; + return ; }; // Sessions section: Default model & agent, Session retention diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 643ed74a..6ed0308b 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -220,7 +220,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain' return mode === 'markdown' ? 'markdown' : 'plain'; }; -export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage'; +export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage'; interface OpenChamberVisualSettingsProps { /** Which settings to show. If undefined, shows all. */ @@ -242,6 +242,8 @@ export const OpenChamberVisualSettings: React.FC const setUserMessageRenderingMode = useUIStore(state => state.setUserMessageRenderingMode); const stickyUserHeader = useUIStore(state => state.stickyUserHeader); const setStickyUserHeader = useUIStore(state => state.setStickyUserHeader); + const wideChatLayoutEnabled = useUIStore(state => state.wideChatLayoutEnabled); + const setWideChatLayoutEnabled = useUIStore(state => state.setWideChatLayoutEnabled); const chatRenderMode = useUIStore(state => state.chatRenderMode); const setChatRenderMode = useUIStore(state => state.setChatRenderMode); const activityRenderMode = useUIStore(state => state.activityRenderMode); @@ -366,6 +368,11 @@ export const OpenChamberVisualSettings: React.FC void updateDesktopSettings({ stickyUserHeader: enabled }); }, [setStickyUserHeader]); + const handleWideChatLayoutChange = React.useCallback((enabled: boolean) => { + setWideChatLayoutEnabled(enabled); + void updateDesktopSettings({ wideChatLayoutEnabled: enabled }); + }, [setWideChatLayoutEnabled]); + const handleShowSplitAssistantMessageActionsChange = React.useCallback((enabled: boolean) => { setShowSplitAssistantMessageActions(enabled); void updateDesktopSettings({ showSplitAssistantMessageActions: enabled }); @@ -469,6 +476,7 @@ export const OpenChamberVisualSettings: React.FC || shouldShow('messageTransport') || (shouldShow('activityRenderMode') && chatRenderMode === 'sorted') || shouldShow('stickyUserHeader') + || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || shouldShow('diffLayout') || (shouldShow('mobileStatusBar') && isMobile) @@ -1451,7 +1459,7 @@ export const OpenChamberVisualSettings: React.FC )} - {(shouldShow('stickyUserHeader') || shouldShow('splitAssistantMessageActions') || (shouldShow('mobileStatusBar') && isMobile) || shouldShow('dotfiles') || shouldShow('queueMode') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && ( + {(shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || (shouldShow('mobileStatusBar') && isMobile) || shouldShow('dotfiles') || shouldShow('queueMode') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
{shouldShow('reasoning') && (
)} + {shouldShow('wideChatLayout') && ( +
handleWideChatLayoutChange(!wideChatLayoutEnabled)} + onKeyDown={(event) => { + if (event.key === ' ' || event.key === 'Enter') { + event.preventDefault(); + handleWideChatLayoutChange(!wideChatLayoutEnabled); + } + }} + > + + {t('settings.openchamber.visual.field.wideChatLayout')} +
+ )} + {shouldShow('splitAssistantMessageActions') && (
{ if (typeof settings.stickyUserHeader === 'boolean' && settings.stickyUserHeader !== store.stickyUserHeader) { store.setStickyUserHeader(settings.stickyUserHeader); } + if (typeof settings.wideChatLayoutEnabled === 'boolean' && settings.wideChatLayoutEnabled !== store.wideChatLayoutEnabled) { + store.setWideChatLayoutEnabled(settings.wideChatLayoutEnabled); + } if ( typeof settings.showSplitAssistantMessageActions === 'boolean' && settings.showSplitAssistantMessageActions !== store.showSplitAssistantMessageActions @@ -859,6 +862,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (typeof candidate.stickyUserHeader === 'boolean') { result.stickyUserHeader = candidate.stickyUserHeader; } + if (typeof candidate.wideChatLayoutEnabled === 'boolean') { + result.wideChatLayoutEnabled = candidate.wideChatLayoutEnabled; + } if (typeof candidate.showSplitAssistantMessageActions === 'boolean') { result.showSplitAssistantMessageActions = candidate.showSplitAssistantMessageActions; } diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index e2165ab7..cda78712 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -563,6 +563,7 @@ interface UIStore { showTerminalQuickKeysOnDesktop: boolean; persistChatDraft: boolean; inputSpellcheckEnabled: boolean; + wideChatLayoutEnabled: boolean; showToolFileIcons: boolean; showExpandedBashTools: boolean; showExpandedEditTools: boolean; @@ -689,6 +690,7 @@ interface UIStore { setMaxLastMessageLength: (value: number) => void; setPersistChatDraft: (value: boolean) => void; setInputSpellcheckEnabled: (value: boolean) => void; + setWideChatLayoutEnabled: (value: boolean) => void; setShowToolFileIcons: (value: boolean) => void; setShowExpandedBashTools: (value: boolean) => void; setShowExpandedEditTools: (value: boolean) => void; @@ -811,6 +813,7 @@ export const useUIStore = create()( showTerminalQuickKeysOnDesktop: false, persistChatDraft: true, inputSpellcheckEnabled: false, + wideChatLayoutEnabled: false, showToolFileIcons: true, showExpandedBashTools: false, showExpandedEditTools: false, @@ -1804,6 +1807,9 @@ export const useUIStore = create()( setInputSpellcheckEnabled: (value) => { set({ inputSpellcheckEnabled: value }); }, + setWideChatLayoutEnabled: (value) => { + set({ wideChatLayoutEnabled: value }); + }, setShowToolFileIcons: (value) => { set({ showToolFileIcons: value }); }, @@ -2025,6 +2031,7 @@ export const useUIStore = create()( maxLastMessageLength: state.maxLastMessageLength, persistChatDraft: state.persistChatDraft, inputSpellcheckEnabled: state.inputSpellcheckEnabled, + wideChatLayoutEnabled: state.wideChatLayoutEnabled, showToolFileIcons: state.showToolFileIcons, showExpandedBashTools: state.showExpandedBashTools, showExpandedEditTools: state.showExpandedEditTools, diff --git a/packages/ui/src/styles/typography.css b/packages/ui/src/styles/typography.css index 3d26a381..cd4dd21c 100644 --- a/packages/ui/src/styles/typography.css +++ b/packages/ui/src/styles/typography.css @@ -25,11 +25,15 @@ } .chat-column { - width: min(100%, 64rem); + width: min(100%, 48rem); margin-inline: auto; padding-inline: calc(clamp(0.75rem, 2.5vw, 1rem) * var(--padding-scale, 1)); } + .wide-chat-layout .chat-column { + width: min(100%, 64rem); + } + .chat-input-column { width: min(100%, 48rem); margin-inline: auto; @@ -37,11 +41,15 @@ } .chat-message-column { - width: min(100%, 64rem); + width: min(100%, 48rem); margin-inline: auto; padding-inline: calc(clamp(0.75rem, 2.5vw, 1rem) * var(--padding-scale, 1)); } + .wide-chat-layout .chat-message-column { + width: min(100%, 64rem); + } + @media (min-width: 1024px) { .chat-column { padding-inline: calc(clamp(1rem, 2.5vw, 1.5rem) * var(--padding-scale, 1));