diff --git a/CHANGELOG.md b/CHANGELOG.md index e49fdc74a..1171e9473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ - macOS menu: device list now shows connected nodes only. - macOS menu: device rows now pack platform/version on the first line, and command lists wrap in submenus. - macOS menu: split device platform/version across first and second rows for better fit. +- macOS Canvas: show remote control status in the debug overlay and log A2UI auto-nav decisions. - iOS node: fix ReplayKit screen recording crash caused by queue isolation assertions during capture. - iOS Talk Mode: avoid audio tap queue assertions when starting recognition. - macOS: use $HOME/Library/pnpm for SSH PATH exports (thanks @mbelinky). diff --git a/apps/macos/Sources/Clawdis/CanvasManager.swift b/apps/macos/Sources/Clawdis/CanvasManager.swift index 32163744b..cdee59703 100644 --- a/apps/macos/Sources/Clawdis/CanvasManager.swift +++ b/apps/macos/Sources/Clawdis/CanvasManager.swift @@ -151,8 +151,22 @@ final class CanvasManager { private func handleGatewayPush(_ push: GatewayPush) { guard case let .snapshot(snapshot) = push else { return } - let a2uiUrl = Self.resolveA2UIHostUrl(from: snapshot.canvashosturl) - guard let controller = self.panelController else { return } + let raw = snapshot.canvashosturl?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + if raw.isEmpty { + Self.logger.debug("canvas host url missing in gateway snapshot") + } else { + Self.logger.debug("canvas host url snapshot=\(raw, privacy: .public)") + } + let a2uiUrl = Self.resolveA2UIHostUrl(from: raw) + if a2uiUrl == nil, !raw.isEmpty { + Self.logger.debug("canvas host url invalid; cannot resolve A2UI") + } + guard let controller = self.panelController else { + if a2uiUrl != nil { + Self.logger.debug("canvas panel not visible; skipping auto-nav") + } + return + } self.maybeAutoNavigateToA2UI(controller: controller, a2uiUrl: a2uiUrl) } @@ -169,7 +183,12 @@ final class CanvasManager { private func maybeAutoNavigateToA2UI(controller: CanvasWindowController, a2uiUrl: String?) { guard let a2uiUrl else { return } - guard controller.shouldAutoNavigateToA2UI(lastAutoTarget: self.lastAutoA2UIUrl) else { return } + let shouldNavigate = controller.shouldAutoNavigateToA2UI(lastAutoTarget: self.lastAutoA2UIUrl) + guard shouldNavigate else { + Self.logger.debug("canvas auto-nav skipped; target unchanged") + return + } + Self.logger.debug("canvas auto-nav -> \(a2uiUrl, privacy: .public)") controller.load(target: a2uiUrl) self.lastAutoA2UIUrl = a2uiUrl } @@ -182,8 +201,29 @@ final class CanvasManager { func refreshDebugStatus() { guard let controller = self.panelController else { return } let enabled = AppStateStore.shared.debugPaneEnabled - let title = GatewayProcessManager.shared.status.label - let subtitle = AppStateStore.shared.connectionMode.rawValue + let mode = AppStateStore.shared.connectionMode + let title: String? + let subtitle: String? + switch mode { + case .remote: + title = "Remote control" + switch ControlChannel.shared.state { + case .connected: + subtitle = "Connected" + case .connecting: + subtitle = "Connecting…" + case .disconnected: + subtitle = "Disconnected" + case let .degraded(message): + subtitle = message.isEmpty ? "Degraded" : message + } + case .local: + title = GatewayProcessManager.shared.status.label + subtitle = mode.rawValue + case .unconfigured: + title = "Unconfigured" + subtitle = mode.rawValue + } controller.updateDebugStatus(enabled: enabled, title: title, subtitle: subtitle) } diff --git a/apps/macos/Sources/Clawdis/ControlChannel.swift b/apps/macos/Sources/Clawdis/ControlChannel.swift index 51c97a6c3..87eb6847a 100644 --- a/apps/macos/Sources/Clawdis/ControlChannel.swift +++ b/apps/macos/Sources/Clawdis/ControlChannel.swift @@ -52,7 +52,23 @@ final class ControlChannel { case degraded(String) } - private(set) var state: ConnectionState = .disconnected + private(set) var state: ConnectionState = .disconnected { + didSet { + CanvasManager.shared.refreshDebugStatus() + guard oldValue != state else { return } + switch state { + case .connected: + self.logger.info("control channel state -> connected") + case .connecting: + self.logger.info("control channel state -> connecting") + case .disconnected: + self.logger.info("control channel state -> disconnected") + case let .degraded(message): + let detail = message.isEmpty ? "degraded" : "degraded: \(message)" + self.logger.info("control channel state -> \(detail, privacy: .public)") + } + } + } private(set) var lastPingMs: Double? private let logger = Logger(subsystem: "com.steipete.clawdis", category: "control")