2.5.2 #12

Merged
rune merged 23 commits from 2.5.2 into main 2026-08-17 08:37:04 +02:00
Showing only changes of commit 91f67f891b - Show all commits
+49 -4
View File
@@ -28,6 +28,7 @@ struct ModelInfoView: View {
@Environment(\.dismiss) var dismiss
@Bindable private var settings = SettingsService.shared
@State private var showDescriptionCopied = false
var body: some View {
VStack(spacing: 0) {
@@ -80,13 +81,34 @@ struct ModelInfoView: View {
// lay out). The modal itself already scrolls, so a long description just
// means more scrolling, which sidesteps the whole bug class.
VStack(alignment: .leading, spacing: 6) {
Text("Description")
.font(.subheadline.weight(.medium))
.foregroundColor(.secondary)
HStack(spacing: 6) {
Text("Description")
.font(.subheadline.weight(.medium))
.foregroundColor(.secondary)
Spacer()
// A Copy button instead of .textSelection(.enabled): clicking into
// a multi-line selectable Text hands it real AppKit first-responder
// status, and Escape then gets consumed by that text view's own
// cancelOperation: handling before it ever reaches this modal's
// onExitCommand the beep-instead-of-dismiss bug Rune reported.
// infoRow's single-line values keep .textSelection(.enabled); only
// this multi-line block reproduced the bug.
Button(action: copyDescription) {
HStack(spacing: 3) {
Image(systemName: showDescriptionCopied ? "checkmark" : "doc.on.doc")
.font(.system(size: 11))
if showDescriptionCopied {
Text("Copied!")
.font(.system(size: 11))
}
}
.foregroundColor(showDescriptionCopied ? .green : .secondary)
}
.buttonStyle(.plain)
}
Text(desc)
.font(.body)
.foregroundColor(.primary)
.textSelection(.enabled)
}
.padding(.leading, 4)
}
@@ -212,6 +234,29 @@ struct ModelInfoView: View {
.padding(.vertical, 12)
}
.frame(minWidth: 550, idealWidth: 650, minHeight: 550, idealHeight: 750)
// Nearly every value in this modal has .textSelection(.enabled) (infoRow's value text,
// the description). Once one of those has text-selection focus, Escape can get
// intercepted by AppKit's text-selection machinery instead of reaching the close
// button's .keyboardShortcut(.escape) no action is bound there, so it just beeps
// instead of dismissing. onExitCommand is macOS's dedicated hook for the "Escape/Cancel"
// user command and fires regardless of which child currently holds focus, so it's a more
// reliable place to handle this than a single button's keyboardShortcut alone.
.onExitCommand { dismiss() }
}
private func copyDescription() {
guard let desc = model.description else { return }
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(desc, forType: .string)
withAnimation {
showDescriptionCopied = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation {
showDescriptionCopied = false
}
}
}
// MARK: - Layout Helpers