From 4556d52a60b8e9e54548f9657c84b61f10df3d3f Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Thu, 28 May 2026 16:36:15 +0200 Subject: [PATCH] fix view mode toggle --- .../frontend/src/contexts/ViewModeContext.tsx | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/client/ui/frontend/src/contexts/ViewModeContext.tsx b/client/ui/frontend/src/contexts/ViewModeContext.tsx index bfd20804c..828956c19 100644 --- a/client/ui/frontend/src/contexts/ViewModeContext.tsx +++ b/client/ui/frontend/src/contexts/ViewModeContext.tsx @@ -1,4 +1,12 @@ -import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from "react"; +import { + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, + type ReactNode, +} from "react"; import { Window } from "@wailsio/runtime"; import { Preferences } from "@bindings/services"; import { ViewMode as ViewModePref } from "@bindings/preferences/models.js"; @@ -26,6 +34,10 @@ const ViewModeContext = createContext(null); export const ViewModeProvider = ({ children }: { children: ReactNode }) => { const [viewMode, setMode] = useState("default"); + // Mirror of viewMode for dedup inside the async setViewMode without + // adding the state to the callback's dep array (which would re-create + // the callback on every change). + const modeRef = useRef("default"); // Hydrate from the persisted preference. The Go side has already sized // the main window to match (see main.go), so this only catches the @@ -37,6 +49,7 @@ export const ViewModeProvider = ({ children }: { children: ReactNode }) => { if (cancelled) return; const saved = prefs?.viewMode as ViewMode | undefined; if (saved === "default" || saved === "advanced") { + modeRef.current = saved; setMode(saved); } }) @@ -46,25 +59,26 @@ export const ViewModeProvider = ({ children }: { children: ReactNode }) => { }; }, []); - const setViewMode = useCallback( - (mode: ViewMode) => { - setMode((prev) => { - if (prev === mode) return prev; - void (async () => { - // Reuse the live frame height instead of asserting a - // constant — keeps content area stable across switches - // (see VIEW_WIDTH comment above). - const size = await Window.Size().catch(() => null); - const width = VIEW_WIDTH[mode]; - const height = size?.height ?? 640; - await Window.SetSize(width, height).catch(() => {}); - })(); - void Preferences.SetViewMode(mode as unknown as ViewModePref).catch(() => {}); - return mode; - }); - }, - [], - ); + // Resize the window BEFORE flipping React state — otherwise the new + // layout (e.g., advanced-mode right panel mounting) paints into a + // window that hasn't grown yet, causing a brief flex-overflow that + // wobbles the connect toggle's position. Cost: one IPC roundtrip + // (~30ms) before the dropdown checkmark updates. + const setViewMode = useCallback((mode: ViewMode) => { + if (modeRef.current === mode) return; + modeRef.current = mode; + void (async () => { + // Reuse the live frame height instead of asserting a + // constant — keeps content area stable across switches + // (see VIEW_WIDTH comment above). + const size = await Window.Size().catch(() => null); + const width = VIEW_WIDTH[mode]; + const height = size?.height ?? 640; + await Window.SetSize(width, height).catch(() => {}); + setMode(mode); + void Preferences.SetViewMode(mode as unknown as ViewModePref).catch(() => {}); + })(); + }, []); return ( {children}