fix: remove max-width cap on right sidebar drag resize (#727) (#757)

The right sidebar drag was hard-capped at 500px, so long file names
(e.g. construction drawing sheets, multi-part document names) always
truncated with no way to reveal them.

The cap is now window-relative — users can drag the sidebar up to
(window width − 320px reserve) so the left sidebar and editor can
still breathe. Persistence clamp split so the right sidebar keeps a
loose 4000px safety net for corrupted values, while the left sidebar
stays at 500px.
This commit is contained in:
Jinjing 2026-04-17 00:38:47 -07:00 committed by GitHub
parent eced42167f
commit 6bf6a4bda1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 10 deletions

View file

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import { Files, Search, GitBranch, ListChecks, PanelRight } from 'lucide-react'
import { useAppStore } from '@/store'
import { cn } from '@/lib/utils'
@ -21,7 +21,13 @@ import SearchPanel from './Search'
import ChecksPanel from './ChecksPanel'
const MIN_WIDTH = 220
const MAX_WIDTH = 500
// Why: long file names (e.g. construction drawing sheets, multi-part document
// names) used to be truncated at a hard 500px cap that no drag could exceed.
// We now let the user drag up to nearly the full window width and only keep a
// small reserve so the rest of the app (left sidebar, editor) is not squeezed
// to zero — the practical ceiling still scales with the user's window size.
const MIN_NON_SIDEBAR_AREA = 320
const ABSOLUTE_FALLBACK_MAX_WIDTH = 2000
const ACTIVITY_BAR_SIDE_WIDTH = 40
@ -138,11 +144,12 @@ function RightSidebarInner(): React.JSX.Element {
: visibleItems[0].id
const activityBarSideWidth = activityBarPosition === 'side' ? ACTIVITY_BAR_SIDE_WIDTH : 0
const maxWidth = useWindowAwareMaxWidth()
const { containerRef, onResizeStart } = useSidebarResize<HTMLDivElement>({
isOpen: rightSidebarOpen,
width: rightSidebarWidth,
minWidth: MIN_WIDTH,
maxWidth: MAX_WIDTH,
maxWidth,
deltaSign: -1,
renderedExtraWidth: activityBarSideWidth,
setWidth: setRightSidebarWidth
@ -268,6 +275,30 @@ function RightSidebarInner(): React.JSX.Element {
const RightSidebar = React.memo(RightSidebarInner)
export default RightSidebar
// Why: the drag-resize max is a function of window width, not a constant, so
// users with wide displays can expand the sidebar far enough to read long file
// names. Falls back to a large constant in non-DOM environments (tests).
function useWindowAwareMaxWidth(): number {
const [max, setMax] = useState(() => computeMaxRightSidebarWidth())
useEffect(() => {
function update(): void {
setMax(computeMaxRightSidebarWidth())
}
window.addEventListener('resize', update)
return () => window.removeEventListener('resize', update)
}, [])
return max
}
function computeMaxRightSidebarWidth(): number {
if (typeof window === 'undefined' || !Number.isFinite(window.innerWidth)) {
return ABSOLUTE_FALLBACK_MAX_WIDTH
}
return Math.max(MIN_WIDTH, window.innerWidth - MIN_NON_SIDEBAR_AREA)
}
// ─── Status indicator dot color mapping ──────
const STATUS_DOT_COLOR: Record<CheckStatus, string> = {
success: 'bg-emerald-500',

View file

@ -42,12 +42,28 @@ describe('createUISlice hydratePersistedUI', () => {
store.getState().hydratePersistedUI(
makePersistedUI({
sidebarWidth: 100,
rightSidebarWidth: 900
rightSidebarWidth: 100
})
)
expect(store.getState().sidebarWidth).toBe(220)
expect(store.getState().rightSidebarWidth).toBe(500)
expect(store.getState().rightSidebarWidth).toBe(220)
})
it('preserves right sidebar widths above the former 500px cap', () => {
const store = createUIStore()
store.getState().hydratePersistedUI(
makePersistedUI({
sidebarWidth: 260,
rightSidebarWidth: 900
})
)
// Left sidebar stays capped; right sidebar now allows wide drag targets
// so long file names remain readable.
expect(store.getState().sidebarWidth).toBe(260)
expect(store.getState().rightSidebarWidth).toBe(900)
})
it('falls back to existing sidebar widths when persisted values are not finite', () => {

View file

@ -1,3 +1,4 @@
/* eslint-disable max-lines */
import type { StateCreator } from 'zustand'
import type { AppState } from '../types'
import type {
@ -32,13 +33,18 @@ import {
} from '../../../../shared/constants'
const MIN_SIDEBAR_WIDTH = 220
const MAX_SIDEBAR_WIDTH = 500
const MAX_LEFT_SIDEBAR_WIDTH = 500
// Why: the right sidebar drag-resize is window-relative (see right-sidebar
// component), so persisted widths can legitimately be well above the old 500px
// cap on wide displays. Use a large hard ceiling purely as a safety net for
// corrupted/manually-edited values rather than as a product limit.
const MAX_RIGHT_SIDEBAR_WIDTH = 4000
function sanitizePersistedSidebarWidth(width: unknown, fallback: number): number {
function sanitizePersistedSidebarWidth(width: unknown, fallback: number, maxWidth: number): number {
if (typeof width !== 'number' || !Number.isFinite(width)) {
return fallback
}
return Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, width))
return Math.min(maxWidth, Math.max(MIN_SIDEBAR_WIDTH, width))
}
export type UISlice = {
@ -276,8 +282,16 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
// or manually edited. Clamp widths during hydration so invalid values
// cannot push the renderer into broken layouts before the user drags a
// sidebar again.
sidebarWidth: sanitizePersistedSidebarWidth(ui.sidebarWidth, s.sidebarWidth),
rightSidebarWidth: sanitizePersistedSidebarWidth(ui.rightSidebarWidth, s.rightSidebarWidth),
sidebarWidth: sanitizePersistedSidebarWidth(
ui.sidebarWidth,
s.sidebarWidth,
MAX_LEFT_SIDEBAR_WIDTH
),
rightSidebarWidth: sanitizePersistedSidebarWidth(
ui.rightSidebarWidth,
s.rightSidebarWidth,
MAX_RIGHT_SIDEBAR_WIDTH
),
groupBy: ui.groupBy,
sortBy,
// Why: "Active only" is part of the user's sidebar working set, not a