import SwiftUI
/// Equatable wrapper around a focused-value action closure.
///
/// SwiftUI's `focusedSceneValue` / `focusedValue` re-publishes whenever the
/// stored value's identity changes. A bare `() -> Void` closure has no
/// Equatable conformance, so every publisher-view body run looks like a
/// "value changed" event to AppKit, which then rebuilds the system menu and
/// drops open-submenu / hover state. During agent activity the detail view's
/// body runs on every OSC-9 progress tick, so the menu bar rebuilds
/// continuously and becomes hard to use. Wrapping the closure in this
/// Equatable adapter keeps the focused value stable across no-op body runs.
///
/// **Contract**: `token` must hash any captured state that affects the
/// closure's behavior. If the closure captures only stable references
/// (the store, the terminal manager), `token` can stay `nil`. If it captures
/// the selected worktree, a list of targets, an alert payload, etc., set
/// `token` to a hashable projection of those values so a real change triggers
/// a republish.
struct FocusedAction: Equatable {
let isEnabled: Bool
let token: AnyHashable?
private let perform: (Input) -> Void
init(
isEnabled: Bool,
token: AnyHashable? = nil,
perform: @escaping (Input) -> Void
) {
self.isEnabled = isEnabled
self.token = token
self.perform = perform
}
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.isEnabled == rhs.isEnabled && lhs.token == rhs.token
}
func callAsFunction(_ input: Input) {
guard isEnabled else { return }
perform(input)
}
}
extension FocusedAction where Input == Void {
func callAsFunction() {
callAsFunction(())
}
}
extension Optional {
/// Wraps an optional `() -> Void` closure into a `FocusedAction`, preserving
/// the disabled-as-`nil` convention the fork already uses for focused values:
/// a `nil` closure stays `nil` (disabled), a non-`nil` closure becomes an
/// enabled `FocusedAction` carrying `token`.
func asFocusedAction(token: AnyHashable? = nil) -> FocusedAction? where Wrapped == () -> Void {
map { FocusedAction(isEnabled: true, token: token, perform: $0) }
}
}
extension View {
/// Publishes a stable `FocusedAction` through `focusedSceneValue`.
/// Prefer this over a raw closure: AppKit only sees a "value changed"
/// event when `enabled` or `token` flip, instead of on every body run.
func focusedSceneAction(
_ keyPath: WritableKeyPath?>,
enabled: Bool,
token: AnyHashable? = nil,
perform: @escaping (Input) -> Void
) -> some View {
focusedSceneValue(
keyPath,
FocusedAction(isEnabled: enabled, token: token, perform: perform)
)
}
/// `focusedValue` variant. Same contract as `focusedSceneAction`.
func focusedAction(
_ keyPath: WritableKeyPath?>,
enabled: Bool,
token: AnyHashable? = nil,
perform: @escaping (Input) -> Void
) -> some View {
focusedValue(
keyPath,
FocusedAction(isEnabled: enabled, token: token, perform: perform)
)
}
}