diff --git a/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift b/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift index 7e02761f5..1b3a96daa 100644 --- a/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift +++ b/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift @@ -65,6 +65,13 @@ final class VoiceWakeOverlayController: ObservableObject { self.model.isEditing = true } + func cancelEditingAndDismiss() { + self.autoSendTask?.cancel() + self.model.isSending = false + self.model.isEditing = false + self.dismiss(reason: .explicit) + } + func endEditing() { self.model.isEditing = false } @@ -277,6 +284,9 @@ private struct VoiceWakeOverlayView: View { onBeginEditing: { self.controller.userBeganEditing() }, + onEscape: { + self.controller.cancelEditingAndDismiss() + }, onEndEditing: { self.controller.endEditing() }, @@ -348,6 +358,7 @@ private struct TranscriptTextView: NSViewRepresentable { var isFinal: Bool var isOverflowing: Bool var onBeginEditing: () -> Void + var onEscape: () -> Void var onEndEditing: () -> Void var onSend: () -> Void @@ -380,11 +391,12 @@ private struct TranscriptTextView: NSViewRepresentable { .font: NSFont.systemFont(ofSize: 13, weight: .regular), ] textView.focusRingType = .none - textView.onSend = { [weak textView] in - textView?.window?.makeFirstResponder(nil) - self.onSend() - } - textView.onBeginEditing = self.onBeginEditing + textView.onSend = { [weak textView] in + textView?.window?.makeFirstResponder(nil) + self.onSend() + } + textView.onBeginEditing = self.onBeginEditing + textView.onEscape = self.onEscape textView.onEndEditing = self.onEndEditing let scroll = NSScrollView() @@ -504,6 +516,7 @@ private final class TranscriptNSTextView: NSTextView { var onSend: (() -> Void)? var onBeginEditing: (() -> Void)? var onEndEditing: (() -> Void)? + var onEscape: (() -> Void)? override func becomeFirstResponder() -> Bool { self.onBeginEditing?() @@ -518,6 +531,11 @@ private final class TranscriptNSTextView: NSTextView { override func keyDown(with event: NSEvent) { let isReturn = event.keyCode == 36 + let isEscape = event.keyCode == 53 + if isEscape { + self.onEscape?() + return + } if isReturn && event.modifierFlags.contains(.command) { self.onSend?() return diff --git a/src/cli/program.ts b/src/cli/program.ts index 4ee65fc17..d45d5da7c 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -8,6 +8,10 @@ import { statusCommand } from "../commands/status.js"; import { loadConfig } from "../config/config.js"; import { danger, info, setVerbose } from "../globals.js"; import { getResolvedLoggerSettings } from "../logging.js"; +import { + readLatestHeartbeat, + tailHeartbeatEvents, +} from "../process/heartbeat-events.js"; import { loginWeb, logoutWeb, @@ -23,10 +27,6 @@ import { resolveHeartbeatSeconds, resolveReconnectPolicy, } from "../web/reconnect.js"; -import { - readLatestHeartbeat, - tailHeartbeatEvents, -} from "../process/heartbeat-events.js"; import { ensureWebChatServerFromConfig, startWebChatServer, diff --git a/src/process/heartbeat-events.ts b/src/process/heartbeat-events.ts index bdf3083fb..9307a37bb 100644 --- a/src/process/heartbeat-events.ts +++ b/src/process/heartbeat-events.ts @@ -6,12 +6,7 @@ import readline from "node:readline"; export type HeartbeatEvent = { type: "heartbeat"; ts: number; // epoch ms - status: - | "sent" - | "ok-empty" - | "ok-token" - | "skipped" - | "failed"; + status: "sent" | "ok-empty" | "ok-token" | "skipped" | "failed"; to?: string; preview?: string; durationMs?: number;