fix: 自带刷新的弹窗不提高采样频率,过期风扇数据不显示启动中

- AI 用量、显示器弹窗及仅含这两项的合并面板沿用后台采样间隔
- 风扇读取失败沿用旧数据时清除 isStarting,避免“启动中”超出 10 秒窗口

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ysicingandClaude authored and 缘生 committed 2026-10-04 21:52:14 +08:00
1 parent 8919dabb6c
commit 8244dd15c1
6 files changed
+117 -4

No files matched your search

+5 -1
View File
@@ -167,7 +167,11 @@ its own `NSStatusItem` (created in reverse so they read left to right) and opens
popover for that metric; in the *combined* layout a single item opens `CombinedPopoverView`: a status
overview of compact metric rows. Clicking a row expands its existing `PopoverDetail` inline;
only one row expands at a time. Summary rows remain visible, so their sampling demand is retained
alongside the expanded detail demand. The header keeps the main-window and settings
alongside the expanded detail demand. Only visible system-metric rows raise the overview's
sampling rate; an empty overview or one containing only AI usage and displays retains the
configured background interval unless a live window or system-metric detail requires more.
Standalone AI usage and display popovers likewise keep the background interval.
The header keeps the main-window and settings
actions outside the scroll area; no bottom action bar is shown. Header and content contribute to
the same measured panel height. Collapsing or switching a row releases its
detail-only demand without rebuilding the panel. No timer or continuous animation is added.
@@ -146,9 +146,14 @@ public final class AppModel {
// 仅实时监控页需要每秒采样;历史、工具和设置页沿用用户设置的后台间隔。
let liveWindow = window && [.overview, .system, .cpu, .gpu, .memory, .disk,
.network, .thermal, .battery].contains(tab)
// AI 用量与显示器有各自的刷新机制,不读取采样结果,不应提高全局采样频率。
let selfRefreshing: Set<MenuBarItem> = [.aiUsage, .display]
let livePopover = popover.map { !selfRefreshing.contains($0) } ?? false
// 合并面板的摘要始终可见;只有其中的系统指标需要提高全局采样频率。
let liveOverview = !overview.subtracting(selfRefreshing).isEmpty
// 进程页要读全系统进程(启动 ps),每 2 秒刷新一次足够,也更省电
demand.interval = processPage && popover == nil ? .seconds(2)
: liveWindow || popover != nil || isCombinedPopoverOpen ? .seconds(1) : .seconds(settings.refreshSeconds)
demand.interval = processPage && !livePopover ? .seconds(2)
: liveWindow || livePopover || liveOverview ? .seconds(1) : .seconds(settings.refreshSeconds)
demand.memory = true
demand.network = true
let showing = { (page: PanelTab, item: MenuBarItem) in (window && tab == page) || popover == item }
@@ -136,7 +136,13 @@ public final class MetricsStore {
var byGroup = Dictionary(uniqueKeysWithValues: old.temperatures.map { ($0.group, $0) })
for summary in new.temperatures { byGroup[summary.group] = summary }
let ordered = TemperatureGroup.allCases.compactMap { byGroup[$0] }
sensors = SensorReadings(temperatures: ordered, fans: fanCount == 0 ? [] : (new.fans.isEmpty ? old.fans : new.fans), fanCount: fanCount)
// 读取失败时沿用的旧风扇数据不再推进起步窗口,去掉“启动中”以免提示超出时限。
let fans = new.fans.isEmpty ? old.fans.map { fan in
var fan = fan
fan.isStarting = false
return fan
} : new.fans
sensors = SensorReadings(temperatures: ordered, fans: fanCount == 0 ? [] : fans, fanCount: fanCount)
}
var fastestFan: FanState? {
@@ -55,4 +55,20 @@ struct FanAvailabilityTests {
store.apply(snapshot)
#expect(store.supportsFans)
}
@Test func staleFansReusedAfterReadFailureDropStartingState() {
let store = MetricsStore()
var snapshot = MetricsSnapshot()
let starting = FanState(id: 0, current: 0, minimum: 0, maximum: 6000, target: 2000, isManual: true,
isStarting: true)
snapshot.sensors = SensorReadings(temperatures: [], fans: [starting], fanCount: 1)
store.apply(snapshot)
#expect(store.sensors?.fans.isStartingUp == true)
// 读取失败返回空数组:沿用旧读数,但不再显示“启动中”。
snapshot.sensors = SensorReadings(temperatures: [], fans: [])
store.apply(snapshot)
#expect(store.sensors?.fans.count == 1)
#expect(store.sensors?.fans.first?.target == 2000)
#expect(store.sensors?.fans.isStartingUp == false)
}
}
@@ -84,6 +84,74 @@ struct MenuBarLayoutTests {
}
}
@Test(arguments: [MenuBarLayout.combined, .iconOnly])
func independentRefreshItemsPreserveBackgroundSampling(layout: MenuBarLayout) throws {
try model { model, _ in
model.settings.menuBarLayout = layout
model.settings.refreshSeconds = 5
model.settings.aiUsageEnabled = true
let selections: [Set<MenuBarItem>] = [[], [.display], [.aiUsage], [.display, .aiUsage]]
for items in selections {
model.settings.menuBarItems = items
let idle = model.demand
#expect(idle.interval == .seconds(5))
model.isCombinedPopoverOpen = true
#expect(model.demand == idle)
for item in items {
model.combinedPopoverTab = item
#expect(model.demand == idle)
if item == .display { #expect(model.displayControlsVisible) }
}
model.combinedPopoverTab = nil
#expect(model.demand == idle)
model.isCombinedPopoverOpen = false
#expect(model.demand == idle)
#expect(!model.displayControlsVisible)
}
}
}
@Test(arguments: [MenuBarLayout.combined, .iconOnly])
func systemMetricsStillRefreshWhileDisplayDetailsAreExpanded(layout: MenuBarLayout) throws {
try model { model, _ in
model.settings.menuBarLayout = layout
model.settings.refreshSeconds = 5
var snapshot = MetricsSnapshot()
snapshot.sensors = SensorReadings(temperatures: [], fans: [], fanCount: 1)
model.store.apply(snapshot)
for item: MenuBarItem in [.cpu, .memory, .network, .gpu, .disk, .temperature, .fan, .battery] {
model.settings.menuBarItems = [.display, item]
let idle = model.demand
model.isCombinedPopoverOpen = true
#expect(model.demand.interval == .seconds(1))
model.combinedPopoverTab = .display
#expect(model.demand.interval == .seconds(1))
#expect(model.displayControlsVisible)
model.isCombinedPopoverOpen = false
model.combinedPopoverTab = nil
#expect(model.demand == idle)
}
}
}
@Test func mainWindowDemandStillOverridesIndependentPanelRefresh() throws {
try model { model, _ in
model.settings.menuBarLayout = .iconOnly
model.settings.menuBarItems = [.display]
model.settings.refreshSeconds = 5
model.isCombinedPopoverOpen = true
model.combinedPopoverTab = .display
model.isMainWindowVisible = true
model.settings.panelTab = .cpu
#expect(model.demand.interval == .seconds(1))
model.settings.panelTab = .settingsGeneral
#expect(model.demand.interval == .seconds(5))
model.settings.processesEnabled = true
model.settings.panelTab = .processes
#expect(model.demand.interval == .seconds(2))
}
}
@Test func compactOverviewHasNoHalfEmptyRowsAndExpandsInline() throws {
try model { model, _ in
model.settings.menuBarItems = [.cpu, .memory, .network, .disk, .battery]
@@ -522,6 +522,20 @@ private func isolatedDefaults() -> UserDefaults {
#expect(model.demand.interval == .seconds(1))
}
@Test func popoversWithoutLiveMetricsKeepSamplingInterval() {
let model = model()
model.settings.refreshSeconds = 5
for item: MenuBarItem in [.aiUsage, .display] {
model.openPopover = item
#expect(model.demand.interval == .seconds(5), "\(item) popover should not boost sampling")
}
model.settings.processesEnabled = true
model.isMainWindowVisible = true
model.settings.panelTab = .processes
#expect(model.demand.interval == .seconds(2))
}
@Test func networkDetailVisibility() {
let model = model()
#expect(!model.isNetworkDetailVisible)