refactor: replace canvas.show with canvas.present
This commit is contained in:
@@ -282,7 +282,7 @@ class NodeRuntime(context: Context) {
|
||||
|
||||
val invokeCommands =
|
||||
buildList {
|
||||
add(ClawdisCanvasCommand.Show.rawValue)
|
||||
add(ClawdisCanvasCommand.Present.rawValue)
|
||||
add(ClawdisCanvasCommand.Hide.rawValue)
|
||||
add(ClawdisCanvasCommand.Navigate.rawValue)
|
||||
add(ClawdisCanvasCommand.Eval.rawValue)
|
||||
@@ -558,7 +558,11 @@ class NodeRuntime(context: Context) {
|
||||
}
|
||||
|
||||
return when (command) {
|
||||
ClawdisCanvasCommand.Show.rawValue -> BridgeSession.InvokeResult.ok(null)
|
||||
ClawdisCanvasCommand.Present.rawValue -> {
|
||||
val url = CanvasController.parseNavigateUrl(paramsJson)
|
||||
canvas.navigate(url)
|
||||
BridgeSession.InvokeResult.ok(null)
|
||||
}
|
||||
ClawdisCanvasCommand.Hide.rawValue -> BridgeSession.InvokeResult.ok(null)
|
||||
ClawdisCanvasCommand.Navigate.rawValue -> {
|
||||
val url = CanvasController.parseNavigateUrl(paramsJson)
|
||||
|
||||
@@ -8,7 +8,7 @@ enum class ClawdisCapability(val rawValue: String) {
|
||||
}
|
||||
|
||||
enum class ClawdisCanvasCommand(val rawValue: String) {
|
||||
Show("canvas.show"),
|
||||
Present("canvas.present"),
|
||||
Hide("canvas.hide"),
|
||||
Navigate("canvas.navigate"),
|
||||
Eval("canvas.eval"),
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.junit.Test
|
||||
class ClawdisProtocolConstantsTest {
|
||||
@Test
|
||||
fun canvasCommandsUseStableStrings() {
|
||||
assertEquals("canvas.show", ClawdisCanvasCommand.Show.rawValue)
|
||||
assertEquals("canvas.present", ClawdisCanvasCommand.Present.rawValue)
|
||||
assertEquals("canvas.hide", ClawdisCanvasCommand.Hide.rawValue)
|
||||
assertEquals("canvas.navigate", ClawdisCanvasCommand.Navigate.rawValue)
|
||||
assertEquals("canvas.eval", ClawdisCanvasCommand.Eval.rawValue)
|
||||
|
||||
@@ -173,7 +173,7 @@ final class BridgeConnectionController {
|
||||
|
||||
private func currentCommands() -> [String] {
|
||||
var commands: [String] = [
|
||||
ClawdisCanvasCommand.show.rawValue,
|
||||
ClawdisCanvasCommand.present.rawValue,
|
||||
ClawdisCanvasCommand.hide.rawValue,
|
||||
ClawdisCanvasCommand.navigate.rawValue,
|
||||
ClawdisCanvasCommand.evalJS.rawValue,
|
||||
|
||||
@@ -387,7 +387,15 @@ final class NodeAppModel {
|
||||
|
||||
do {
|
||||
switch command {
|
||||
case ClawdisCanvasCommand.show.rawValue:
|
||||
case ClawdisCanvasCommand.present.rawValue:
|
||||
let params = (try? Self.decodeParams(ClawdisCanvasPresentParams.self, from: req.paramsJSON)) ??
|
||||
ClawdisCanvasPresentParams()
|
||||
let url = params.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if url.isEmpty {
|
||||
self.screen.showDefaultCanvas()
|
||||
} else {
|
||||
self.screen.navigate(to: url)
|
||||
}
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
|
||||
case ClawdisCanvasCommand.hide.rawValue:
|
||||
|
||||
@@ -299,7 +299,7 @@ struct SettingsTab: View {
|
||||
|
||||
private func currentCommands() -> [String] {
|
||||
var commands: [String] = [
|
||||
ClawdisCanvasCommand.show.rawValue,
|
||||
ClawdisCanvasCommand.present.rawValue,
|
||||
ClawdisCanvasCommand.hide.rawValue,
|
||||
ClawdisCanvasCommand.navigate.rawValue,
|
||||
ClawdisCanvasCommand.evalJS.rawValue,
|
||||
|
||||
@@ -95,8 +95,8 @@ enum ControlRequestHandler {
|
||||
deliver: deliver,
|
||||
to: to)
|
||||
|
||||
case let .canvasShow(session, path, placement):
|
||||
return await self.handleCanvasShow(session: session, path: path, placement: placement)
|
||||
case let .canvasPresent(session, path, placement):
|
||||
return await self.handleCanvasPresent(session: session, path: path, placement: placement)
|
||||
|
||||
case let .canvasHide(session):
|
||||
return await self.handleCanvasHide(session: session)
|
||||
@@ -235,7 +235,7 @@ enum ControlRequestHandler {
|
||||
UserDefaults.standard.object(forKey: cameraEnabledKey) as? Bool ?? false
|
||||
}
|
||||
|
||||
private static func handleCanvasShow(
|
||||
private static func handleCanvasPresent(
|
||||
session: String,
|
||||
path: String?,
|
||||
placement: CanvasPlacement?) async -> Response
|
||||
@@ -243,20 +243,24 @@ enum ControlRequestHandler {
|
||||
guard self.canvasEnabled() else { return Response(ok: false, message: "Canvas disabled by user") }
|
||||
_ = session
|
||||
do {
|
||||
var params: [String: Any] = [:]
|
||||
if let path, !path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
_ = try await self.invokeLocalNode(
|
||||
command: ClawdisCanvasCommand.navigate.rawValue,
|
||||
params: ["url": path],
|
||||
timeoutMs: 20000)
|
||||
} else {
|
||||
_ = try await self.invokeLocalNode(
|
||||
command: ClawdisCanvasCommand.show.rawValue,
|
||||
params: nil,
|
||||
timeoutMs: 20000)
|
||||
params["url"] = path
|
||||
}
|
||||
if placement != nil {
|
||||
return Response(ok: true, message: "Canvas placement ignored (node mode)")
|
||||
if let placement {
|
||||
var placementPayload: [String: Any] = [:]
|
||||
if let x = placement.x { placementPayload["x"] = x }
|
||||
if let y = placement.y { placementPayload["y"] = y }
|
||||
if let width = placement.width { placementPayload["width"] = width }
|
||||
if let height = placement.height { placementPayload["height"] = height }
|
||||
if !placementPayload.isEmpty {
|
||||
params["placement"] = placementPayload
|
||||
}
|
||||
}
|
||||
_ = try await self.invokeLocalNode(
|
||||
command: ClawdisCanvasCommand.present.rawValue,
|
||||
params: params.isEmpty ? nil : params,
|
||||
timeoutMs: 20000)
|
||||
return Response(ok: true)
|
||||
} catch {
|
||||
return Response(ok: false, message: error.localizedDescription)
|
||||
|
||||
@@ -489,7 +489,7 @@ struct DebugSettings: View {
|
||||
.font(.caption.monospaced())
|
||||
.frame(width: 160)
|
||||
Button("Show panel") {
|
||||
Task { await self.canvasShow() }
|
||||
Task { await self.canvasPresent() }
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
Button("Hide panel") {
|
||||
@@ -750,7 +750,7 @@ extension DebugSettings {
|
||||
// MARK: - Canvas debug actions
|
||||
|
||||
@MainActor
|
||||
private func canvasShow() async {
|
||||
private func canvasPresent() async {
|
||||
self.canvasError = nil
|
||||
let session = self.canvasSessionKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
do {
|
||||
|
||||
@@ -107,7 +107,7 @@ final class MacNodeModeCoordinator {
|
||||
|
||||
private func currentCommands(caps: [String]) -> [String] {
|
||||
var commands: [String] = [
|
||||
ClawdisCanvasCommand.show.rawValue,
|
||||
ClawdisCanvasCommand.present.rawValue,
|
||||
ClawdisCanvasCommand.hide.rawValue,
|
||||
ClawdisCanvasCommand.navigate.rawValue,
|
||||
ClawdisCanvasCommand.evalJS.rawValue,
|
||||
|
||||
@@ -19,9 +19,19 @@ actor MacNodeRuntime {
|
||||
}
|
||||
do {
|
||||
switch command {
|
||||
case ClawdisCanvasCommand.show.rawValue:
|
||||
case ClawdisCanvasCommand.present.rawValue:
|
||||
let params = (try? Self.decodeParams(ClawdisCanvasPresentParams.self, from: req.paramsJSON)) ??
|
||||
ClawdisCanvasPresentParams()
|
||||
let urlTrimmed = params.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let url = urlTrimmed.isEmpty ? nil : urlTrimmed
|
||||
let placement = params.placement.map {
|
||||
CanvasPlacement(x: $0.x, y: $0.y, width: $0.width, height: $0.height)
|
||||
}
|
||||
try await MainActor.run {
|
||||
_ = try CanvasManager.shared.show(sessionKey: "main", path: nil)
|
||||
_ = try CanvasManager.shared.showDetailed(
|
||||
sessionKey: "main",
|
||||
target: url,
|
||||
placement: placement)
|
||||
}
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
|
||||
|
||||
@@ -319,12 +319,12 @@ struct ClawdisCLI {
|
||||
private static func parseCanvas(args: inout [String]) throws -> ParsedCLIRequest {
|
||||
guard let sub = args.popFirst() else { throw CLIError.help }
|
||||
switch sub {
|
||||
case "show":
|
||||
case "present":
|
||||
var session = "main"
|
||||
var target: String?
|
||||
let placement = self.parseCanvasPlacement(args: &args, session: &session, target: &target)
|
||||
return ParsedCLIRequest(
|
||||
request: .canvasShow(session: session, path: target, placement: placement),
|
||||
request: .canvasPresent(session: session, path: target, placement: placement),
|
||||
kind: .generic)
|
||||
case "a2ui":
|
||||
return try self.parseCanvasA2UI(args: &args)
|
||||
@@ -542,7 +542,7 @@ struct ClawdisCLI {
|
||||
return
|
||||
}
|
||||
|
||||
if case .canvasShow = parsed.request {
|
||||
if case .canvasPresent = parsed.request {
|
||||
if let message = response.message, !message.isEmpty {
|
||||
FileHandle.standardOutput.write(Data((message + "\n").utf8))
|
||||
}
|
||||
@@ -759,7 +759,7 @@ struct ClawdisCLI {
|
||||
clawdis-mac node invoke --node <id> --command <name> [--params-json <json>]
|
||||
|
||||
Canvas:
|
||||
clawdis-mac canvas show [--session <key>] [--target </...|https://...|file://...>]
|
||||
clawdis-mac canvas present [--session <key>] [--target </...|https://...|file://...>]
|
||||
[--x <screenX> --y <screenY>] [--width <w> --height <h>]
|
||||
clawdis-mac canvas a2ui push --jsonl <path> [--session <key>] # A2UI v0.8 JSONL
|
||||
clawdis-mac canvas a2ui reset [--session <key>]
|
||||
|
||||
@@ -122,7 +122,7 @@ public enum Request: Sendable {
|
||||
case status
|
||||
case agent(message: String, thinking: String?, session: String?, deliver: Bool, to: String?)
|
||||
case rpcStatus
|
||||
case canvasShow(session: String, path: String?, placement: CanvasPlacement?)
|
||||
case canvasPresent(session: String, path: String?, placement: CanvasPlacement?)
|
||||
case canvasHide(session: String)
|
||||
case canvasEval(session: String, javaScript: String)
|
||||
case canvasSnapshot(session: String, outPath: String?)
|
||||
@@ -185,7 +185,7 @@ extension Request: Codable {
|
||||
case status
|
||||
case agent
|
||||
case rpcStatus
|
||||
case canvasShow
|
||||
case canvasPresent
|
||||
case canvasHide
|
||||
case canvasEval
|
||||
case canvasSnapshot
|
||||
@@ -236,8 +236,8 @@ extension Request: Codable {
|
||||
case .rpcStatus:
|
||||
try container.encode(Kind.rpcStatus, forKey: .type)
|
||||
|
||||
case let .canvasShow(session, path, placement):
|
||||
try container.encode(Kind.canvasShow, forKey: .type)
|
||||
case let .canvasPresent(session, path, placement):
|
||||
try container.encode(Kind.canvasPresent, forKey: .type)
|
||||
try container.encode(session, forKey: .session)
|
||||
try container.encodeIfPresent(path, forKey: .path)
|
||||
try container.encodeIfPresent(placement, forKey: .placement)
|
||||
@@ -338,11 +338,11 @@ extension Request: Codable {
|
||||
case .rpcStatus:
|
||||
self = .rpcStatus
|
||||
|
||||
case .canvasShow:
|
||||
case .canvasPresent:
|
||||
let session = try container.decode(String.self, forKey: .session)
|
||||
let path = try container.decodeIfPresent(String.self, forKey: .path)
|
||||
let placement = try container.decodeIfPresent(CanvasPlacement.self, forKey: .placement)
|
||||
self = .canvasShow(session: session, path: path, placement: placement)
|
||||
self = .canvasPresent(session: session, path: path, placement: placement)
|
||||
|
||||
case .canvasHide:
|
||||
let session = try container.decode(String.self, forKey: .session)
|
||||
|
||||
@@ -140,7 +140,7 @@ struct ControlRequestHandlerTests {
|
||||
func canvasRequestsReturnDisabledWhenCanvasDisabled() async throws {
|
||||
let show = try await Self.withDefaultOverride(pauseDefaultsKey, value: false) {
|
||||
try await Self.withDefaultOverride(canvasEnabledKey, value: false) {
|
||||
try await ControlRequestHandler.process(request: .canvasShow(session: "s", path: nil, placement: nil))
|
||||
try await ControlRequestHandler.process(request: .canvasPresent(session: "s", path: nil, placement: nil))
|
||||
}
|
||||
}
|
||||
#expect(show.ok == false)
|
||||
|
||||
@@ -8,6 +8,30 @@ public struct ClawdisCanvasNavigateParams: Codable, Sendable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClawdisCanvasPlacement: Codable, Sendable, Equatable {
|
||||
public var x: Double?
|
||||
public var y: Double?
|
||||
public var width: Double?
|
||||
public var height: Double?
|
||||
|
||||
public init(x: Double? = nil, y: Double? = nil, width: Double? = nil, height: Double? = nil) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClawdisCanvasPresentParams: Codable, Sendable, Equatable {
|
||||
public var url: String?
|
||||
public var placement: ClawdisCanvasPlacement?
|
||||
|
||||
public init(url: String? = nil, placement: ClawdisCanvasPlacement? = nil) {
|
||||
self.url = url
|
||||
self.placement = placement
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClawdisCanvasEvalParams: Codable, Sendable, Equatable {
|
||||
public var javaScript: String
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public enum ClawdisCanvasCommand: String, Codable, Sendable {
|
||||
case show = "canvas.show"
|
||||
case present = "canvas.present"
|
||||
case hide = "canvas.hide"
|
||||
case navigate = "canvas.navigate"
|
||||
case evalJS = "canvas.eval"
|
||||
|
||||
@@ -119,7 +119,7 @@ Add to `src/gateway/protocol/schema.ts` (and regenerate Swift models):
|
||||
|
||||
### Node command set (canvas)
|
||||
These are values for `node.invoke.command`:
|
||||
- `canvas.show` / `canvas.hide`
|
||||
- `canvas.present` / `canvas.hide`
|
||||
- `canvas.navigate` with `{ url }` (loads a URL; use `""` or `"/"` to return to the default canvas/A2UI scaffold)
|
||||
- `canvas.eval` with `{ javaScript }`
|
||||
- `canvas.snapshot` with `{ maxWidth?, quality?, format? }`
|
||||
|
||||
@@ -96,7 +96,7 @@ Related:
|
||||
|
||||
`clawdis-mac` exposes Canvas via the control socket. For agent use, prefer `--json` so you can read the structured `CanvasShowResult` (including `status`).
|
||||
|
||||
- `clawdis-mac canvas show [--session <key>] [--target <...>] [--x/--y/--width/--height]`
|
||||
- `clawdis-mac canvas present [--session <key>] [--target <...>] [--x/--y/--width/--height]`
|
||||
- Local targets map into the session directory via the custom scheme (directory targets resolve `index.html|index.htm`).
|
||||
- If `/` has no index file, Canvas shows the built-in A2UI shell and returns `status: "a2uiShell"`.
|
||||
- `clawdis-mac canvas hide [--session <key>]`
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- Avoid double-sending actions when the bundled A2UI shell is present (let the shell forward clicks so it can resolve richer context).
|
||||
- Intercept `clawdis://…` navigations inside the Canvas WKWebView and route them through `DeepLinkHandler` (no NSWorkspace bounce).
|
||||
- `GatewayConnection` auto-starts the local gateway (and retries briefly) when a request fails in `.local` mode, so Canvas actions don’t silently fail if the gateway isn’t running yet.
|
||||
- Fix a crash that made `clawdis-mac canvas show`/`eval` look “hung”:
|
||||
- Fix a crash that made `clawdis-mac canvas present`/`eval` look “hung”:
|
||||
- `VoicePushToTalkHotkey`’s NSEvent monitor could call `@MainActor` code off-main, triggering executor checks / EXC_BAD_ACCESS on macOS 26.2.
|
||||
- Now it hops back to the main actor before mutating state.
|
||||
- Preserve in-page state when closing Canvas (hide the window instead of closing the `WKWebView`).
|
||||
|
||||
@@ -120,10 +120,11 @@ function pickDefaultNode(nodes: NodeListNode[]): NodeListNode | null {
|
||||
const candidates = connected.length > 0 ? connected : withCanvas;
|
||||
if (candidates.length === 1) return candidates[0];
|
||||
|
||||
const local = candidates.filter((n) =>
|
||||
n.platform?.toLowerCase().startsWith("mac") &&
|
||||
typeof n.nodeId === "string" &&
|
||||
n.nodeId.startsWith("mac-"),
|
||||
const local = candidates.filter(
|
||||
(n) =>
|
||||
n.platform?.toLowerCase().startsWith("mac") &&
|
||||
typeof n.nodeId === "string" &&
|
||||
n.nodeId.startsWith("mac-"),
|
||||
);
|
||||
if (local.length === 1) return local[0];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user