From 33c082cd5bee9e1728db2cb472ba86d0f4994432 Mon Sep 17 00:00:00 2001 From: Zane <75694352+zanesq@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:06:56 -0700 Subject: [PATCH] Fix a few ui edge cases - refresh occasionally crashing, chat loader over text and chat input height returning to auto (#3469) --- ui/desktop/openapi.json | 2 +- ui/desktop/src/components/BaseChat.tsx | 3 -- ui/desktop/src/components/ChatInput.tsx | 9 +++-- ui/desktop/src/components/pair.tsx | 2 +- .../components/sessions/SessionsInsights.tsx | 31 ++++++++++++++-- ui/desktop/src/main.ts | 36 +++++++++++++++++-- ui/desktop/src/preload.ts | 15 +++++++- 7 files changed, 85 insertions(+), 13 deletions(-) diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 329ce854..98be026b 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -10,7 +10,7 @@ "license": { "name": "Apache-2.0" }, - "version": "1.0.36" + "version": "1.1.0" }, "paths": { "/agent/tools": { diff --git a/ui/desktop/src/components/BaseChat.tsx b/ui/desktop/src/components/BaseChat.tsx index 50ad42f5..f26ab7c0 100644 --- a/ui/desktop/src/components/BaseChat.tsx +++ b/ui/desktop/src/components/BaseChat.tsx @@ -446,9 +446,6 @@ function BaseChatContent({ {/* Custom content after messages */} {renderAfterMessages && renderAfterMessages()} - - {/* Bottom padding to make space for the loading indicator */} -
{/* Fixed loading indicator at bottom left of chat container */} diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index a7f2aecc..7467dfe3 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -427,7 +427,6 @@ export default function ChatInput({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, clearAlerts]); - const minHeight = '1rem'; const maxHeight = 10 * 24; // Debounced function to update actual value @@ -456,6 +455,13 @@ export default function ChatInput({ } }, [debouncedAutosize, displayValue]); + // Reset textarea height when displayValue is empty + useEffect(() => { + if (textAreaRef.current && displayValue === '') { + textAreaRef.current.style.height = 'auto'; + } + }, [displayValue]); + const handleChange = (evt: React.ChangeEvent) => { const val = evt.target.value; const cursorPosition = evt.target.selectionStart; @@ -910,7 +916,6 @@ export default function ChatInput({ ref={textAreaRef} rows={1} style={{ - minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px`, overflowY: 'auto', opacity: isRecording ? 0 : 1, diff --git a/ui/desktop/src/components/pair.tsx b/ui/desktop/src/components/pair.tsx index e44d2d8c..b2597672 100644 --- a/ui/desktop/src/components/pair.tsx +++ b/ui/desktop/src/components/pair.tsx @@ -199,7 +199,7 @@ export default function Pair({ onMessageStreamFinish={handleMessageStreamFinish} renderBeforeMessages={renderBeforeMessages} customChatInputProps={customChatInputProps} - contentClassName={cn('pr-1', (isMobile || sidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state + contentClassName={cn('pr-1 pb-10', (isMobile || sidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state showPopularTopics={!isTransitioningFromHub} // Don't show popular topics while transitioning from Hub suppressEmptyState={isTransitioningFromHub} // Suppress all empty state content while transitioning from Hub /> diff --git a/ui/desktop/src/components/sessions/SessionsInsights.tsx b/ui/desktop/src/components/sessions/SessionsInsights.tsx index a3d61b0c..41b0a218 100644 --- a/ui/desktop/src/components/sessions/SessionsInsights.tsx +++ b/ui/desktop/src/components/sessions/SessionsInsights.tsx @@ -23,6 +23,7 @@ export function SessionInsights() { const [error, setError] = useState(null); const [recentSessions, setRecentSessions] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [isLoadingSessions, setIsLoadingSessions] = useState(true); // const [recentProjects, setRecentProjects] = useState([]); const navigate = useNavigate(); @@ -67,7 +68,8 @@ export function SessionInsights() { setRecentSessions(sessions.slice(0, 3)); } catch (error) { console.error('Failed to load recent sessions:', error); - // Don't set loading to false here since insights is the primary data + } finally { + setIsLoadingSessions(false); } }; @@ -400,7 +402,32 @@ export function SessionInsights() {
- {recentSessions.length > 0 ? ( + {isLoadingSessions ? ( + // Show skeleton while sessions are loading + <> +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+ + ) : recentSessions.length > 0 ? ( recentSessions.map((session, index) => (
{ const configStr = JSON.stringify(windowConfig).replace(/'/g, "\\'"); - mainWindow.webContents.executeJavaScript(` - localStorage.setItem('gooseConfig', '${configStr}') - `); + // Add error handling and retry logic for localStorage access + mainWindow.webContents + .executeJavaScript( + ` + try { + if (typeof Storage !== 'undefined' && window.localStorage) { + localStorage.setItem('gooseConfig', '${configStr}'); + } else { + console.warn('localStorage not available, retrying in 100ms'); + setTimeout(() => { + try { + localStorage.setItem('gooseConfig', '${configStr}'); + } catch (e) { + console.error('Failed to set localStorage after retry:', e); + } + }, 100); + } + } catch (e) { + console.error('Failed to access localStorage:', e); + // Retry after a short delay + setTimeout(() => { + try { + localStorage.setItem('gooseConfig', '${configStr}'); + } catch (retryError) { + console.error('Failed to set localStorage after retry:', retryError); + } + }, 100); + } + ` + ) + .catch((error) => { + console.error('Failed to execute localStorage script:', error); + }); }); // Handle new window creation for links diff --git a/ui/desktop/src/preload.ts b/ui/desktop/src/preload.ts index b1b6218f..091c2be9 100644 --- a/ui/desktop/src/preload.ts +++ b/ui/desktop/src/preload.ts @@ -116,7 +116,20 @@ type AppConfigAPI = { const electronAPI: ElectronAPI = { platform: process.platform, reactReady: () => ipcRenderer.send('react-ready'), - getConfig: () => config, + getConfig: () => { + // Add fallback to localStorage if config from preload is empty or missing + if (!config || Object.keys(config).length === 0) { + try { + const storedConfig = localStorage.getItem('gooseConfig'); + if (storedConfig) { + return JSON.parse(storedConfig); + } + } catch (e) { + console.warn('Failed to parse stored config from localStorage:', e); + } + } + return config; + }, hideWindow: () => ipcRenderer.send('hide-window'), directoryChooser: (replace?: boolean) => ipcRenderer.invoke('directory-chooser', replace), createChatWindow: (