| ... | @@ -1,6 +1,12 @@ | ... | @@ -1,6 +1,12 @@ |
| 1 | import { useAtomValue } from "@effect/atom-react"; | 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import * as Schema from "effect/Schema"; | 2 | import * as Schema from "effect/Schema"; |
| 3 | import { useEffect, useState, type CSSProperties, type ReactNode } from "react"; | 3 | import { |
| | 4 | useEffect, |
| | 5 | useState, |
| | 6 | useSyncExternalStore, |
| | 7 | type CSSProperties, |
| | 8 | type ReactNode, |
| | 9 | } from "react"; |
| 4 | import { useLocation, useNavigate } from "@tanstack/react-router"; | 10 | import { useLocation, useNavigate } from "@tanstack/react-router"; |
| 5 | | 11 | |
| 6 | import { isElectron } from "../env"; | 12 | import { isElectron } from "../env"; |
| ... | @@ -31,6 +37,15 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; | ... | @@ -31,6 +37,15 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| 31 | | 37 | |
| 32 | const MACOS_TRAFFIC_LIGHTS_LEFT_INSET = "90px"; | 38 | const MACOS_TRAFFIC_LIGHTS_LEFT_INSET = "90px"; |
| 33 | | 39 | |
| | 40 | function subscribeToViewportWidth(onChange: () => void): () => void { |
| | 41 | window.addEventListener("resize", onChange); |
| | 42 | return () => window.removeEventListener("resize", onChange); |
| | 43 | } |
| | 44 | |
| | 45 | function readViewportWidth(): number { |
| | 46 | return window.innerWidth; |
| | 47 | } |
| | 48 | |
| 34 | function readInitialThreadSidebarWidth(): number { | 49 | function readInitialThreadSidebarWidth(): number { |
| 35 | try { | 50 | try { |
| 36 | return resolveInitialThreadSidebarWidth( | 51 | return resolveInitialThreadSidebarWidth( |
| ... | @@ -109,7 +124,11 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { | ... | @@ -109,7 +124,11 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 109 | const useSidebarV2Theme = useSidebarV2 || isOnSettings; | 124 | const useSidebarV2Theme = useSidebarV2 || isOnSettings; |
| 110 | const isMacosDesktop = isElectron && isMacPlatform(navigator.platform); | 125 | const isMacosDesktop = isElectron && isMacPlatform(navigator.platform); |
| 111 | const [sidebarWidth, setSidebarWidth] = useState(readInitialThreadSidebarWidth); | 126 | const [sidebarWidth, setSidebarWidth] = useState(readInitialThreadSidebarWidth); |
| 112 | const sidebarMaximumWidth = resolveThreadSidebarMaximumWidth(window.innerWidth); | 127 | // Subscribed rather than read once: the clamp must track live window size, |
| | 128 | // and a clamped drag ends with an unchanged width, which skips the re-render |
| | 129 | // that would otherwise refresh a render-time snapshot. |
| | 130 | const viewportWidth = useSyncExternalStore(subscribeToViewportWidth, readViewportWidth); |
| | 131 | const sidebarMaximumWidth = resolveThreadSidebarMaximumWidth(viewportWidth); |
| 113 | const [isWindowFullscreen, setIsWindowFullscreen] = useState(() => { | 132 | const [isWindowFullscreen, setIsWindowFullscreen] = useState(() => { |
| 114 | const getWindowFullscreenState = window.desktopBridge?.getWindowFullscreenState; | 133 | const getWindowFullscreenState = window.desktopBridge?.getWindowFullscreenState; |
| 115 | return isMacosDesktop && typeof getWindowFullscreenState === "function" | 134 | return isMacosDesktop && typeof getWindowFullscreenState === "function" |