fix(ui): stop status-bar disk chips from oscillating

Measure spare width including visible disk chips and apply hysteresis so adaptive slot count no longer flicker.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-12 00:42:33 +08:00
1 parent 883b240f73
commit da95113e6d
1 file changed
+56 -15
+56 -15
View File
@@ -197,6 +197,34 @@ function useRemoteHostMetrics(connectionId: string | undefined, enabled: boolean
return { metrics, stale }
}
const DISK_SLOT_THRESHOLDS = [
{ slots: 5, min: 360 },
{ slots: 4, min: 290 },
{ slots: 3, min: 220 },
{ slots: 2, min: 150 },
{ slots: 1, min: 72 },
{ slots: 0, min: 0 },
] as const
/** Deadband so showing/hiding disks cannot oscillate slot count. */
const DISK_SLOT_HYSTERESIS_PX = 36
function diskSlotsForSpare(spare: number, current: number, withHysteresis: boolean): number {
let next = 0
for (const row of DISK_SLOT_THRESHOLDS) {
if (spare >= row.min) {
next = row.slots
break
}
}
if (!withHysteresis || next === current) return next
if (next > current) {
const need = DISK_SLOT_THRESHOLDS.find((row) => row.slots === next)?.min ?? 0
return spare >= need + DISK_SLOT_HYSTERESIS_PX ? next : current
}
const keep = DISK_SLOT_THRESHOLDS.find((row) => row.slots === current)?.min ?? 0
return spare < keep - DISK_SLOT_HYSTERESIS_PX ? next : current
}
function RemoteHostMetricsStrip({
metrics,
stale,
@@ -210,7 +238,9 @@ function RemoteHostMetricsStrip({
}) {
const { t } = useTranslation('common')
const stripRef = useRef<HTMLDivElement>(null)
const [diskSlots, setDiskSlots] = useState(1)
const disksRef = useRef<HTMLDivElement>(null)
const measuredRef = useRef(false)
const [diskSlots, setDiskSlots] = useState(0)
const osLabel = (metrics?.osPrettyName || fallbackOs || '').trim() || null
const tz = metrics ? timezoneLabel(metrics) : null
@@ -248,14 +278,12 @@ function RemoteHostMetricsStrip({
const updateSlots = () => {
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
const spare = spacer?.clientWidth ?? 0
// ~70px per chip; fullscreen usually has room for top 5.
if (spare >= 360) setDiskSlots(5)
else if (spare >= 290) setDiskSlots(4)
else if (spare >= 220) setDiskSlots(3)
else if (spare >= 150) setDiskSlots(2)
else if (spare >= 72) setDiskSlots(1)
else setDiskSlots(0)
const disksEl = disksRef.current
// spacer shrinks when disks appear — add disks width back so capacity is stable.
const spare = (spacer?.clientWidth ?? 0) + (disksEl?.offsetWidth ?? 0)
const useHysteresis = measuredRef.current
measuredRef.current = true
setDiskSlots((prev) => diskSlotsForSpare(spare, prev, useHysteresis))
}
updateSlots()
@@ -264,7 +292,18 @@ function RemoteHostMetricsStrip({
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
if (spacer) observer.observe(spacer)
return () => observer.disconnect()
}, [rankedDisks.length, osLabel, tz, transferLabel, metrics?.cpuPercent, metrics?.memPercent])
}, [])
// Disk chips mount after first metrics poll; re-check once data arrives.
useEffect(() => {
if (!rankedDisks.length) return
const parent = stripRef.current?.parentElement
if (!parent) return
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
const spare = (spacer?.clientWidth ?? 0) + (disksRef.current?.offsetWidth ?? 0)
setDiskSlots((prev) => diskSlotsForSpare(spare, prev, measuredRef.current))
measuredRef.current = true
}, [rankedDisks.length])
const hasAny =
osLabel ||
@@ -303,11 +342,13 @@ function RemoteHostMetricsStrip({
{transferLabel}
</span>
)}
{visibleDisks.map((d) => (
<span key={d.mount} className={`shrink-0 ${pctClass(d.percent)}`}>
{shortMount(d.mount)} {d.percent}%
</span>
))}
<div ref={disksRef} className="flex shrink-0 items-center gap-2">
{visibleDisks.map((d) => (
<span key={d.mount} className={`shrink-0 ${pctClass(d.percent)}`}>
{shortMount(d.mount)} {d.percent}%
</span>
))}
</div>
</div>
)
}