fix: enforce minimum chat width so the git panel can't squish it (#1526)

The git panel can now be dragged toward full window width, but its max was
reserved against window.innerWidth (ignoring the sidebar), letting the panel
squeeze the chat below a usable width. Clamp the panel against the actual
container width and give the chat column a matching min-width floor.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Johannes du Plessis 2026-06-15 10:52:45 -07:00 • committed by GitHub
parent acae8b9419
commit dfa3fc13b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 26 additions and 14 deletions

View file

@ -78,16 +78,18 @@ const COLLAPSED_STATE_FALSE = "0"
const PANEL_DEFAULT_WIDTH = 420 const PANEL_DEFAULT_WIDTH = 420
const PANEL_MIN_WIDTH = 320 const PANEL_MIN_WIDTH = 320
// Keep at least this much room for the chat so the panel can grow to nearly the // Keep at least this much room for the chat so the panel can grow to nearly the
// full window (e.g. ~50/50 on ultrawide screens) without hiding the chat. // full window (e.g. ~50/50 on ultrawide screens) without squishing the chat.
const PANEL_MIN_CHAT_WIDTH = 360 // Exported so the chat column can enforce the same floor via min-width.
export const PANEL_MIN_CHAT_WIDTH = 360
function getPanelMaxWidth(): number { function getPanelMaxWidth(availableWidth?: number): number {
if (typeof window === "undefined") return PANEL_DEFAULT_WIDTH if (typeof window === "undefined") return PANEL_DEFAULT_WIDTH
return Math.max(PANEL_MIN_WIDTH, window.innerWidth - PANEL_MIN_CHAT_WIDTH) const available = availableWidth ?? window.innerWidth
return Math.max(PANEL_MIN_WIDTH, available - PANEL_MIN_CHAT_WIDTH)
} }
function clampPanelWidth(width: number): number { function clampPanelWidth(width: number, availableWidth?: number): number {
return Math.min(getPanelMaxWidth(), Math.max(PANEL_MIN_WIDTH, width)) return Math.min(getPanelMaxWidth(availableWidth), Math.max(PANEL_MIN_WIDTH, width))
} }
function readStoredPanelWidth(): number { function readStoredPanelWidth(): number {
@ -190,6 +192,7 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
) )
const [width, setWidthState] = useState(() => readStoredPanelWidth()) const [width, setWidthState] = useState(() => readStoredPanelWidth())
const [fullScreen, setFullScreen] = useState(false) const [fullScreen, setFullScreen] = useState(false)
const panelRef = useRef<HTMLDivElement>(null)
const setCollapsed = (next: boolean) => { const setCollapsed = (next: boolean) => {
setCollapsedState(next) setCollapsedState(next)
@ -202,18 +205,20 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
} }
const setWidth = useCallback((next: number) => { const setWidth = useCallback((next: number) => {
const clamped = clampPanelWidth(next) const available = panelRef.current?.parentElement?.clientWidth
const clamped = clampPanelWidth(next, available)
setWidthState(clamped) setWidthState(clamped)
window.localStorage.setItem(PANEL_STORAGE_WIDTH, String(clamped)) window.localStorage.setItem(PANEL_STORAGE_WIDTH, String(clamped))
}, []) }, [])
// Re-clamp the stored width when the window shrinks so the panel never // Re-clamp against the real container width on mount and whenever the window
// exceeds the available space (which would squeeze out the chat). // resizes, so the panel can never squeeze the chat below its minimum width.
useEffect(() => { useEffect(() => {
if (typeof window === "undefined") return if (typeof window === "undefined") return
const onResize = () => setWidth(width) const reclamp = () => setWidth(width)
window.addEventListener("resize", onResize) reclamp()
return () => window.removeEventListener("resize", onResize) window.addEventListener("resize", reclamp)
return () => window.removeEventListener("resize", reclamp)
}, [setWidth, width]) }, [setWidth, width])
const [selectedTreePath, setSelectedTreePath] = useState<string | null>(null) const [selectedTreePath, setSelectedTreePath] = useState<string | null>(null)
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({}) const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
@ -305,6 +310,7 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
return ( return (
<aside <aside
ref={panelRef}
className={cn( className={cn(
"relative flex shrink-0 flex-col bg-[var(--ui-bg)]", "relative flex shrink-0 flex-col bg-[var(--ui-bg)]",
fullScreen ? "fixed inset-0 !w-full" : "h-full" fullScreen ? "fixed inset-0 !w-full" : "h-full"

View file

@ -3,7 +3,10 @@ import { useStreamContext as useAgentThreadStream } from "@langchain/react"
import type { AgentThread, Message } from "@/lib/agents/types" import type { AgentThread, Message } from "@/lib/agents/types"
import type { ModelSelection } from "@/lib/agents/provider/useModelOptions" import type { ModelSelection } from "@/lib/agents/provider/useModelOptions"
import { AgentGitPanel } from "@/components/agents/AgentGitPanel" import {
AgentGitPanel,
PANEL_MIN_CHAT_WIDTH,
} from "@/components/agents/AgentGitPanel"
import { AgentPromptBar } from "@/components/agents/AgentPromptBar" import { AgentPromptBar } from "@/components/agents/AgentPromptBar"
import { Messages } from "@/components/agents/messages" import { Messages } from "@/components/agents/messages"
import { streamMessagesToUi } from "@/lib/agents/streamMessagesToUi" import { streamMessagesToUi } from "@/lib/agents/streamMessagesToUi"
@ -58,7 +61,10 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) {
return ( return (
<div className="flex min-w-0 flex-1"> <div className="flex min-w-0 flex-1">
<div className="flex min-w-0 flex-1 flex-col"> <div
className="flex flex-1 flex-col"
style={{ minWidth: PANEL_MIN_CHAT_WIDTH }}
>
{hasMessages ? ( {hasMessages ? (
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden"> <div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
<Messages <Messages