iOS: centralize canvas commands and capabilities

This commit is contained in:
Peter Steinberger
2025-12-18 02:12:53 +01:00
parent c976799f8c
commit 33bf5cf42a
5 changed files with 77 additions and 9 deletions

View File

@@ -155,17 +155,17 @@ final class BridgeConnectionController {
}
private func currentCaps() -> [String] {
var caps = ["canvas"]
var caps = [ClawdisCapability.canvas.rawValue]
// Default-on: if the key doesn't exist yet, treat it as enabled.
let cameraEnabled =
UserDefaults.standard.object(forKey: "camera.enabled") == nil
? true
: UserDefaults.standard.bool(forKey: "camera.enabled")
if cameraEnabled { caps.append("camera") }
if cameraEnabled { caps.append(ClawdisCapability.camera.rawValue) }
let voiceWakeEnabled = UserDefaults.standard.bool(forKey: VoiceWakePreferences.enabledKey)
if voiceWakeEnabled { caps.append("voiceWake") }
if voiceWakeEnabled { caps.append(ClawdisCapability.voiceWake.rawValue) }
return caps
}

View File

@@ -266,12 +266,7 @@ final class NodeAppModel {
private func handleInvoke(_ req: BridgeInvokeRequest) async -> BridgeInvokeResponse {
// Alias for "canvas" capability: accept canvas.* commands and map them to screen.*.
let command =
if req.command.hasPrefix("canvas.") {
"screen." + req.command.dropFirst("canvas.".count)
} else {
req.command
}
let command = ClawdisInvokeCommandAliases.canonicalizeCanvasToScreen(req.command)
if command.hasPrefix("screen.") || command.hasPrefix("camera."), self.isBackgrounded {
return BridgeInvokeResponse(

View File

@@ -0,0 +1,38 @@
import ClawdisKit
import Testing
@Suite struct CanvasCommandAliasTests {
@Test func mapsKnownCanvasCommandsToScreen() {
let mappings: [(ClawdisCanvasCommand, ClawdisScreenCommand)] = [
(.show, .show),
(.hide, .hide),
(.setMode, .setMode),
(.navigate, .navigate),
(.evalJS, .evalJS),
(.snapshot, .snapshot),
]
for (canvas, screen) in mappings {
#expect(
ClawdisInvokeCommandAliases.canonicalizeCanvasToScreen(canvas.rawValue) ==
screen.rawValue)
}
}
@Test func mapsUnknownCanvasNamespaceToScreen() {
#expect(ClawdisInvokeCommandAliases.canonicalizeCanvasToScreen("canvas.foo") == "screen.foo")
}
@Test func leavesNonCanvasCommandsUnchanged() {
#expect(
ClawdisInvokeCommandAliases.canonicalizeCanvasToScreen(ClawdisCameraCommand.snap.rawValue) ==
ClawdisCameraCommand.snap.rawValue)
}
@Test func capabilitiesUseStableStrings() {
#expect(ClawdisCapability.canvas.rawValue == "canvas")
#expect(ClawdisCapability.camera.rawValue == "camera")
#expect(ClawdisCapability.voiceWake.rawValue == "voiceWake")
}
}

View File

@@ -0,0 +1,28 @@
import Foundation
public enum ClawdisCanvasCommand: String, Codable, Sendable {
case show = "canvas.show"
case hide = "canvas.hide"
case setMode = "canvas.setMode"
case navigate = "canvas.navigate"
case evalJS = "canvas.eval"
case snapshot = "canvas.snapshot"
}
public enum ClawdisInvokeCommandAliases {
public static func canonicalizeCanvasToScreen(_ command: String) -> String {
if command.hasPrefix(ClawdisCanvasCommand.namespacePrefix) {
return ClawdisScreenCommand.namespacePrefix +
command.dropFirst(ClawdisCanvasCommand.namespacePrefix.count)
}
return command
}
}
extension ClawdisCanvasCommand {
public static var namespacePrefix: String { "canvas." }
}
extension ClawdisScreenCommand {
public static var namespacePrefix: String { "screen." }
}

View File

@@ -0,0 +1,7 @@
import Foundation
public enum ClawdisCapability: String, Codable, Sendable {
case canvas
case camera
case voiceWake
}