cmux/Sources/Update/UpdatePill.swift
Lawrence Chen 679cafdc51 Fix update pill constraint feedback loop
The pill never appeared because:
1. SwiftUI .frame(width:0, height:0) when idle poisoned fittingSize
2. AppKit constraints locked at 0x0 prevented expansion on state change
3. fittingSize always returned 0 due to active 0x0 constraints

Fix: Remove zero-frame from SwiftUI (always render at natural size,
use opacity only). Deactivate constraints before measuring fittingSize
so they don't clamp the measurement. Pass visibility to sizeToolbarItem
to set constraints to zero when idle or natural size when active.
2026-02-08 20:21:27 -08:00

66 lines
2 KiB
Swift

import AppKit
import Foundation
import SwiftUI
/// A pill-shaped button that displays update status and provides access to update actions.
struct UpdatePill: View {
@ObservedObject var model: UpdateViewModel
@State private var showPopover = false
private let textFont = NSFont.systemFont(ofSize: 11, weight: .medium)
var body: some View {
let state = model.effectiveState
let visible = !state.isIdle
pillButton
.popover(
isPresented: $showPopover,
attachmentAnchor: .rect(.bounds),
arrowEdge: .top
) {
UpdatePopoverView(model: model)
}
.opacity(visible ? 1 : 0)
}
@ViewBuilder
private var pillButton: some View {
Button(action: {
if case .notFound(let notFound) = model.state {
model.state = .idle
notFound.acknowledgement()
} else {
showPopover.toggle()
}
}) {
HStack(spacing: 6) {
UpdateBadge(model: model)
.frame(width: 14, height: 14)
Text(model.text)
.font(Font(textFont))
.lineLimit(1)
.truncationMode(.tail)
.frame(width: textWidth)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(
Capsule()
.fill(model.backgroundColor)
)
.foregroundColor(model.foregroundColor)
.contentShape(Capsule())
}
.buttonStyle(.plain)
.help(model.text)
.accessibilityLabel(model.text)
.accessibilityIdentifier("UpdatePill")
}
private var textWidth: CGFloat? {
let attributes: [NSAttributedString.Key: Any] = [.font: textFont]
let size = (model.maxWidthText as NSString).size(withAttributes: attributes)
return size.width
}
}