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:
1 parent
883b240f73
commit
da95113e6d
1 file changed
+51
-10
@@ -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,12 +342,14 @@ function RemoteHostMetricsStrip({
|
||||
{transferLabel}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user