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 } 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({ function RemoteHostMetricsStrip({
metrics, metrics,
stale, stale,
@@ -210,7 +238,9 @@ function RemoteHostMetricsStrip({
}) { }) {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const stripRef = useRef<HTMLDivElement>(null) 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 osLabel = (metrics?.osPrettyName || fallbackOs || '').trim() || null
const tz = metrics ? timezoneLabel(metrics) : null const tz = metrics ? timezoneLabel(metrics) : null
@@ -248,14 +278,12 @@ function RemoteHostMetricsStrip({
const updateSlots = () => { const updateSlots = () => {
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]') const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
const spare = spacer?.clientWidth ?? 0 const disksEl = disksRef.current
// ~70px per chip; fullscreen usually has room for top 5. // spacer shrinks when disks appear — add disks width back so capacity is stable.
if (spare >= 360) setDiskSlots(5) const spare = (spacer?.clientWidth ?? 0) + (disksEl?.offsetWidth ?? 0)
else if (spare >= 290) setDiskSlots(4) const useHysteresis = measuredRef.current
else if (spare >= 220) setDiskSlots(3) measuredRef.current = true
else if (spare >= 150) setDiskSlots(2) setDiskSlots((prev) => diskSlotsForSpare(spare, prev, useHysteresis))
else if (spare >= 72) setDiskSlots(1)
else setDiskSlots(0)
} }
updateSlots() updateSlots()
@@ -264,7 +292,18 @@ function RemoteHostMetricsStrip({
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]') const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
if (spacer) observer.observe(spacer) if (spacer) observer.observe(spacer)
return () => observer.disconnect() 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 = const hasAny =
osLabel || osLabel ||
@@ -303,11 +342,13 @@ function RemoteHostMetricsStrip({
{transferLabel} {transferLabel}
</span> </span>
)} )}
{visibleDisks.map((d) => ( <div ref={disksRef} className="flex shrink-0 items-center gap-2">
<span key={d.mount} className={`shrink-0 ${pctClass(d.percent)}`}> {visibleDisks.map((d) => (
{shortMount(d.mount)} {d.percent}% <span key={d.mount} className={`shrink-0 ${pctClass(d.percent)}`}>
</span> {shortMount(d.mount)} {d.percent}%
))} </span>
))}
</div>
</div> </div>
) )
} }