Files
Qiu Yuzhou c708cd8d19 feat(ui): debounce transient status rows via shared display gate
The home run-control card and the menu-bar status card render the agent
and system-proxy status rows straight from snapshot replacements, so fast
operations (e.g. proxy mode switches) flash transient states like
"applying" before settling.

Add a presentation-layer debounce shared by both surfaces:

- TransientDelayGate: per-stream gate where stable values display
  immediately, transient values (starting/applying/repairing) display
  after 400 ms and are dropped when superseded, and displayed transients
  are held for at least 400 ms.
- StatusDisplayModel: app-level shared model deriving agent and
  system-proxy payloads (row + detail + hint) from workflow snapshots
  through two independent gates; domain facts keep their cadence.
- Wire the model through the composition root into RuntimeControlCard
  and StatusCardView; toggles, approval actions, the difference view and
  target/mode rows keep reading the raw snapshot.
2026-10-24 02:22:39 +08:00

29 lines
897 B
Swift

import Foundation
/// 顺序虚拟 sleep 记录器:调用前先 `armNextCall()` 预约一对门闩,下一次
/// sleep 记录延迟、报到后挂起在预约门闩上;测试据此按调用序推进虚拟时钟,
/// 不轮询、不真实等待。
@MainActor
final class SleepGateRecorder {
private(set) var delays: [TimeInterval] = []
private var armedEntered: AsyncGate?
private var armedRelease: AsyncGate?
func sleep(_ delay: TimeInterval) async {
delays.append(delay)
let entered = armedEntered ?? AsyncGate()
let release = armedRelease ?? AsyncGate()
armedEntered = nil
armedRelease = nil
entered.release()
await release.wait()
}
func armNextCall() -> (entered: AsyncGate, release: AsyncGate) {
let pair = (entered: AsyncGate(), release: AsyncGate())
armedEntered = pair.entered
armedRelease = pair.release
return pair
}
}