Unify tab/workspace action naming in CLI and socket
This commit is contained in:
parent
10e44396df
commit
a5360adb38
3 changed files with 265 additions and 18 deletions
107
CLI/cmux.swift
107
CLI/cmux.swift
|
|
@ -1344,6 +1344,53 @@ struct CMUXCLI {
|
|||
throw CLIError(message: "Surface index not found")
|
||||
}
|
||||
|
||||
private func canonicalSurfaceHandleFromTabInput(_ value: String) -> String {
|
||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let pieces = trimmed.split(separator: ":", omittingEmptySubsequences: false)
|
||||
guard pieces.count == 2,
|
||||
String(pieces[0]).lowercased() == "tab",
|
||||
let ordinal = Int(String(pieces[1])) else {
|
||||
return trimmed
|
||||
}
|
||||
return "surface:\(ordinal)"
|
||||
}
|
||||
|
||||
private func normalizeTabHandle(
|
||||
_ raw: String?,
|
||||
client: SocketClient,
|
||||
workspaceHandle: String? = nil,
|
||||
allowFocused: Bool = false
|
||||
) throws -> String? {
|
||||
guard let raw else {
|
||||
return try normalizeSurfaceHandle(
|
||||
nil,
|
||||
client: client,
|
||||
workspaceHandle: workspaceHandle,
|
||||
allowFocused: allowFocused
|
||||
)
|
||||
}
|
||||
|
||||
let canonical = canonicalSurfaceHandleFromTabInput(raw)
|
||||
return try normalizeSurfaceHandle(
|
||||
canonical,
|
||||
client: client,
|
||||
workspaceHandle: workspaceHandle,
|
||||
allowFocused: false
|
||||
)
|
||||
}
|
||||
|
||||
private func displayTabHandle(_ raw: String?) -> String? {
|
||||
guard let raw else { return nil }
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let pieces = trimmed.split(separator: ":", omittingEmptySubsequences: false)
|
||||
guard pieces.count == 2,
|
||||
String(pieces[0]).lowercased() == "surface",
|
||||
let ordinal = Int(String(pieces[1])) else {
|
||||
return trimmed
|
||||
}
|
||||
return "tab:\(ordinal)"
|
||||
}
|
||||
|
||||
private func formatHandle(_ payload: [String: Any], kind: String, idFormat: CLIIDFormat) -> String? {
|
||||
let id = payload["\(kind)_id"] as? String
|
||||
let ref = payload["\(kind)_ref"] as? String
|
||||
|
|
@ -1360,6 +1407,40 @@ struct CMUXCLI {
|
|||
}
|
||||
}
|
||||
|
||||
private func formatTabHandle(_ payload: [String: Any], idFormat: CLIIDFormat) -> String? {
|
||||
let id = (payload["tab_id"] as? String) ?? (payload["surface_id"] as? String)
|
||||
let refRaw = (payload["tab_ref"] as? String) ?? (payload["surface_ref"] as? String)
|
||||
let ref = displayTabHandle(refRaw)
|
||||
switch idFormat {
|
||||
case .refs:
|
||||
return ref ?? id
|
||||
case .uuids:
|
||||
return id ?? ref
|
||||
case .both:
|
||||
if let ref, let id {
|
||||
return "\(ref) (\(id))"
|
||||
}
|
||||
return ref ?? id
|
||||
}
|
||||
}
|
||||
|
||||
private func formatCreatedTabHandle(_ payload: [String: Any], idFormat: CLIIDFormat) -> String? {
|
||||
let id = (payload["created_tab_id"] as? String) ?? (payload["created_surface_id"] as? String)
|
||||
let refRaw = (payload["created_tab_ref"] as? String) ?? (payload["created_surface_ref"] as? String)
|
||||
let ref = displayTabHandle(refRaw)
|
||||
switch idFormat {
|
||||
case .refs:
|
||||
return ref ?? id
|
||||
case .uuids:
|
||||
return id ?? ref
|
||||
case .both:
|
||||
if let ref, let id {
|
||||
return "\(ref) (\(id))"
|
||||
}
|
||||
return ref ?? id
|
||||
}
|
||||
}
|
||||
|
||||
private func printV2Payload(
|
||||
_ payload: [String: Any],
|
||||
jsonOutput: Bool,
|
||||
|
|
@ -1591,10 +1672,14 @@ struct CMUXCLI {
|
|||
|
||||
let action = actionRaw.lowercased().replacingOccurrences(of: "-", with: "_")
|
||||
let workspaceArg = workspaceOpt ?? (windowOverride == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil)
|
||||
let tabArg = tabOpt ?? surfaceOpt ?? (workspaceOpt == nil && windowOverride == nil ? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"] : nil)
|
||||
let tabArg = tabOpt
|
||||
?? surfaceOpt
|
||||
?? (workspaceOpt == nil && windowOverride == nil
|
||||
? (ProcessInfo.processInfo.environment["CMUX_TAB_ID"] ?? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"])
|
||||
: nil)
|
||||
|
||||
let workspaceId = try normalizeWorkspaceHandle(workspaceArg, client: client, allowCurrent: true)
|
||||
let surfaceId = try normalizeSurfaceHandle(tabArg, client: client, workspaceHandle: workspaceId, allowFocused: true)
|
||||
let surfaceId = try normalizeTabHandle(tabArg, client: client, workspaceHandle: workspaceId, allowFocused: true)
|
||||
|
||||
let inferredTitle = positional.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let title = (titleOpt ?? (inferredTitle.isEmpty ? nil : inferredTitle))?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
|
@ -1619,7 +1704,7 @@ struct CMUXCLI {
|
|||
|
||||
let payload = try client.sendV2(method: "tab.action", params: params)
|
||||
var summaryParts = ["OK", "action=\(action)"]
|
||||
if let tabHandle = formatHandle(payload, kind: "surface", idFormat: idFormat) {
|
||||
if let tabHandle = formatTabHandle(payload, idFormat: idFormat) {
|
||||
summaryParts.append("tab=\(tabHandle)")
|
||||
}
|
||||
if let workspaceHandle = formatHandle(payload, kind: "workspace", idFormat: idFormat) {
|
||||
|
|
@ -1628,10 +1713,8 @@ struct CMUXCLI {
|
|||
if let closed = payload["closed"] {
|
||||
summaryParts.append("closed=\(closed)")
|
||||
}
|
||||
if let created = payload["created_surface_ref"] as? String {
|
||||
if let created = formatCreatedTabHandle(payload, idFormat: idFormat) {
|
||||
summaryParts.append("created=\(created)")
|
||||
} else if let createdId = payload["created_surface_id"] as? String {
|
||||
summaryParts.append("created=\(createdId)")
|
||||
}
|
||||
printV2Payload(payload, jsonOutput: jsonOutput, idFormat: idFormat, fallbackText: summaryParts.joined(separator: " "))
|
||||
}
|
||||
|
|
@ -2944,16 +3027,16 @@ struct CMUXCLI {
|
|||
|
||||
Flags:
|
||||
--action <name> Action name (required if not positional)
|
||||
--tab <id|ref|index> Target tab (alias: --surface)
|
||||
--surface <id|ref|index> Alias for --tab
|
||||
--tab <id|ref|index> Target tab (accepts tab:<n> or surface:<n>; alias: --surface)
|
||||
--surface <id|ref|index> Alias for --tab (backward compatibility)
|
||||
--workspace <id|ref|index> Workspace context (default: current/$CMUX_WORKSPACE_ID)
|
||||
--title <text> Title for rename
|
||||
--url <url> Optional URL for new-browser-right
|
||||
|
||||
Example:
|
||||
cmux tab-action --tab surface:3 --action pin
|
||||
cmux tab-action --tab tab:3 --action pin
|
||||
cmux tab-action --action close-right
|
||||
cmux tab-action --tab surface:2 --action rename --title "build logs"
|
||||
cmux tab-action --tab tab:2 --action rename --title "build logs"
|
||||
"""
|
||||
case "new-workspace":
|
||||
return """
|
||||
|
|
@ -4202,6 +4285,7 @@ struct CMUXCLI {
|
|||
|
||||
Handle Inputs:
|
||||
For most v2-backed commands you can use UUIDs, short refs (window:1/workspace:2/pane:3/surface:4), or indexes.
|
||||
`tab-action` also accepts `tab:<n>` in addition to `surface:<n>`.
|
||||
Output defaults to refs; pass --id-format uuids or --id-format both to include UUIDs.
|
||||
|
||||
Commands:
|
||||
|
|
@ -4227,7 +4311,7 @@ struct CMUXCLI {
|
|||
close-surface [--surface <id|ref>] [--workspace <id|ref>]
|
||||
move-surface --surface <id|ref|index> [--pane <id|ref|index>] [--workspace <id|ref|index>] [--window <id|ref|index>] [--before <id|ref|index>] [--after <id|ref|index>] [--index <n>] [--focus <true|false>]
|
||||
reorder-surface --surface <id|ref|index> (--index <n> | --before <id|ref|index> | --after <id|ref|index>)
|
||||
tab-action --action <name> [--tab <id|ref|index>] [--workspace <id|ref|index>] [--title <text>] [--url <url>]
|
||||
tab-action --action <name> [--tab <id|ref|index>] [--surface <id|ref|index>] [--workspace <id|ref|index>] [--title <text>] [--url <url>]
|
||||
drag-surface-to-split --surface <id|ref> <left|right|up|down>
|
||||
refresh-surfaces
|
||||
surface-health [--workspace <id|ref>]
|
||||
|
|
@ -4318,6 +4402,7 @@ struct CMUXCLI {
|
|||
Environment:
|
||||
CMUX_WORKSPACE_ID Auto-set in cmux terminals. Used as default --workspace for
|
||||
ALL commands (send, list-panels, new-split, notify, etc.).
|
||||
CMUX_TAB_ID Optional alias used by `tab-action` as default --tab.
|
||||
CMUX_SURFACE_ID Auto-set in cmux terminals. Used as default --surface.
|
||||
CMUX_SOCKET_PATH Override the default Unix socket path (/tmp/cmux.sock).
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1193,6 +1193,8 @@ class TerminalController {
|
|||
"pane_ref": v2Ref(kind: .pane, uuid: paneUUID),
|
||||
"surface_id": v2OrNull(surfaceUUID?.uuidString),
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceUUID),
|
||||
"tab_id": v2OrNull(surfaceUUID?.uuidString),
|
||||
"tab_ref": v2TabRef(uuid: surfaceUUID),
|
||||
"surface_type": v2OrNull(surfaceUUID.flatMap { ws.panels[$0]?.panelType.rawValue }),
|
||||
"is_browser_surface": v2OrNull(surfaceUUID.flatMap { ws.panels[$0]?.panelType == .browser })
|
||||
]
|
||||
|
|
@ -1208,7 +1210,7 @@ class TerminalController {
|
|||
var resolvedCaller: [String: Any]? = nil
|
||||
if let callerObj = params["caller"] as? [String: Any],
|
||||
let wsId = v2UUIDAny(callerObj["workspace_id"]) {
|
||||
let surfaceId = v2UUIDAny(callerObj["surface_id"])
|
||||
let surfaceId = v2UUIDAny(callerObj["surface_id"]) ?? v2UUIDAny(callerObj["tab_id"])
|
||||
v2MainSync {
|
||||
let callerTabManager = AppDelegate.shared?.tabManagerFor(tabId: wsId) ?? tabManager
|
||||
if let ws = callerTabManager.tabs.first(where: { $0.id == wsId }) {
|
||||
|
|
@ -1224,6 +1226,8 @@ class TerminalController {
|
|||
let paneUUID = ws.paneId(forPanelId: surfaceId)?.id
|
||||
payload["surface_id"] = surfaceId.uuidString
|
||||
payload["surface_ref"] = v2Ref(kind: .surface, uuid: surfaceId)
|
||||
payload["tab_id"] = surfaceId.uuidString
|
||||
payload["tab_ref"] = v2TabRef(uuid: surfaceId)
|
||||
payload["surface_type"] = v2OrNull(ws.panels[surfaceId]?.panelType.rawValue)
|
||||
payload["is_browser_surface"] = v2OrNull(ws.panels[surfaceId]?.panelType == .browser)
|
||||
payload["pane_id"] = v2OrNull(paneUUID?.uuidString)
|
||||
|
|
@ -1231,6 +1235,8 @@ class TerminalController {
|
|||
} else {
|
||||
payload["surface_id"] = NSNull()
|
||||
payload["surface_ref"] = NSNull()
|
||||
payload["tab_id"] = NSNull()
|
||||
payload["tab_ref"] = NSNull()
|
||||
payload["surface_type"] = NSNull()
|
||||
payload["is_browser_surface"] = NSNull()
|
||||
payload["pane_id"] = NSNull()
|
||||
|
|
@ -1332,6 +1338,13 @@ class TerminalController {
|
|||
return id
|
||||
}
|
||||
}
|
||||
// Tab refs are aliases for surface refs in tab-facing APIs.
|
||||
let trimmed = handle.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
if trimmed.hasPrefix("tab:"),
|
||||
let ordinal = Int(trimmed.replacingOccurrences(of: "tab:", with: "")),
|
||||
let id = v2UUIDByRef[.surface]?["surface:\(ordinal)"] {
|
||||
return id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1340,6 +1353,12 @@ class TerminalController {
|
|||
return v2EnsureHandleRef(kind: kind, uuid: uuid)
|
||||
}
|
||||
|
||||
private func v2TabRef(uuid: UUID?) -> Any {
|
||||
guard let uuid else { return NSNull() }
|
||||
let surfaceRef = v2EnsureHandleRef(kind: .surface, uuid: uuid)
|
||||
return surfaceRef.replacingOccurrences(of: "surface:", with: "tab:")
|
||||
}
|
||||
|
||||
private func v2RefreshKnownRefs() {
|
||||
guard let app = AppDelegate.shared else { return }
|
||||
|
||||
|
|
@ -2041,7 +2060,7 @@ class TerminalController {
|
|||
return
|
||||
}
|
||||
|
||||
let surfaceId = v2UUID(params, "surface_id") ?? workspace.focusedPanelId
|
||||
let surfaceId = v2UUID(params, "surface_id") ?? v2UUID(params, "tab_id") ?? workspace.focusedPanelId
|
||||
guard let surfaceId else {
|
||||
result = .err(code: "not_found", message: "No focused tab", data: nil)
|
||||
return
|
||||
|
|
@ -2049,7 +2068,9 @@ class TerminalController {
|
|||
guard workspace.panels[surfaceId] != nil else {
|
||||
result = .err(code: "not_found", message: "Tab not found", data: [
|
||||
"surface_id": surfaceId.uuidString,
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceId)
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceId),
|
||||
"tab_id": surfaceId.uuidString,
|
||||
"tab_ref": v2TabRef(uuid: surfaceId)
|
||||
])
|
||||
return
|
||||
}
|
||||
|
|
@ -2065,7 +2086,9 @@ class TerminalController {
|
|||
"workspace_id": workspace.id.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: workspace.id),
|
||||
"surface_id": surfaceId.uuidString,
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceId)
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceId),
|
||||
"tab_id": surfaceId.uuidString,
|
||||
"tab_ref": v2TabRef(uuid: surfaceId)
|
||||
]
|
||||
if let paneId = workspace.paneId(forPanelId: surfaceId)?.id {
|
||||
payload["pane_id"] = paneId.uuidString
|
||||
|
|
@ -2169,7 +2192,9 @@ class TerminalController {
|
|||
_ = workspace.reorderSurface(panelId: newPanel.id, toIndex: targetIndex)
|
||||
finish([
|
||||
"created_surface_id": newPanel.id.uuidString,
|
||||
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id)
|
||||
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id),
|
||||
"created_tab_id": newPanel.id.uuidString,
|
||||
"created_tab_ref": v2TabRef(uuid: newPanel.id)
|
||||
])
|
||||
|
||||
case "new_terminal_right", "new_terminal_to_right", "new_terminal_tab_to_right":
|
||||
|
|
@ -2187,7 +2212,9 @@ class TerminalController {
|
|||
_ = workspace.reorderSurface(panelId: newPanel.id, toIndex: targetIndex)
|
||||
finish([
|
||||
"created_surface_id": newPanel.id.uuidString,
|
||||
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id)
|
||||
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id),
|
||||
"created_tab_id": newPanel.id.uuidString,
|
||||
"created_tab_ref": v2TabRef(uuid: newPanel.id)
|
||||
])
|
||||
|
||||
case "new_browser_right", "new_browser_to_right", "new_browser_tab_to_right":
|
||||
|
|
@ -2212,7 +2239,9 @@ class TerminalController {
|
|||
_ = workspace.reorderSurface(panelId: newPanel.id, toIndex: targetIndex)
|
||||
finish([
|
||||
"created_surface_id": newPanel.id.uuidString,
|
||||
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id)
|
||||
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id),
|
||||
"created_tab_id": newPanel.id.uuidString,
|
||||
"created_tab_ref": v2TabRef(uuid: newPanel.id)
|
||||
])
|
||||
|
||||
case "close_left", "close_to_left":
|
||||
|
|
|
|||
133
tests_v2/test_tab_workspace_action_naming.py
Normal file
133
tests_v2/test_tab_workspace_action_naming.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Regression: tab/workspace action naming is consistent in CLI + socket v2."""
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from cmux import cmux, cmuxError
|
||||
|
||||
|
||||
SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock")
|
||||
|
||||
|
||||
def _must(cond: bool, msg: str) -> None:
|
||||
if not cond:
|
||||
raise cmuxError(msg)
|
||||
|
||||
|
||||
def _find_cli_binary() -> str:
|
||||
env_cli = os.environ.get("CMUXTERM_CLI")
|
||||
if env_cli and os.path.isfile(env_cli) and os.access(env_cli, os.X_OK):
|
||||
return env_cli
|
||||
|
||||
fixed = os.path.expanduser("~/Library/Developer/Xcode/DerivedData/cmux-tests-v2/Build/Products/Debug/cmux")
|
||||
if os.path.isfile(fixed) and os.access(fixed, os.X_OK):
|
||||
return fixed
|
||||
|
||||
candidates = glob.glob(os.path.expanduser("~/Library/Developer/Xcode/DerivedData/**/Build/Products/Debug/cmux"), recursive=True)
|
||||
candidates += glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux")
|
||||
candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)]
|
||||
if not candidates:
|
||||
raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI")
|
||||
candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def _run_cli(cli: str, args: List[str], json_output: bool) -> str:
|
||||
env = dict(os.environ)
|
||||
env.pop("CMUX_WORKSPACE_ID", None)
|
||||
env.pop("CMUX_SURFACE_ID", None)
|
||||
env.pop("CMUX_TAB_ID", None)
|
||||
|
||||
cmd = [cli, "--socket", SOCKET_PATH]
|
||||
if json_output:
|
||||
cmd.append("--json")
|
||||
cmd.extend(args)
|
||||
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, check=False, env=env)
|
||||
if proc.returncode != 0:
|
||||
merged = f"{proc.stdout}\n{proc.stderr}".strip()
|
||||
raise cmuxError(f"CLI failed ({' '.join(cmd)}): {merged}")
|
||||
return proc.stdout
|
||||
|
||||
|
||||
def _run_cli_json(cli: str, args: List[str]) -> Dict:
|
||||
output = _run_cli(cli, args, json_output=True)
|
||||
try:
|
||||
return json.loads(output or "{}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise cmuxError(f"Invalid JSON output for {' '.join(args)}: {output!r} ({exc})")
|
||||
|
||||
|
||||
def _focused_surface_ref(c: cmux, workspace_id: str) -> str:
|
||||
current = c._call("surface.current", {"workspace_id": workspace_id}) or {}
|
||||
surface_ref = str(current.get("surface_ref") or "")
|
||||
if surface_ref.startswith("surface:"):
|
||||
return surface_ref
|
||||
|
||||
listed = c._call("surface.list", {"workspace_id": workspace_id}) or {}
|
||||
rows = listed.get("surfaces") or []
|
||||
for row in rows:
|
||||
if bool(row.get("focused")):
|
||||
ref = str(row.get("ref") or "")
|
||||
if ref.startswith("surface:"):
|
||||
return ref
|
||||
for row in rows:
|
||||
ref = str(row.get("ref") or "")
|
||||
if ref.startswith("surface:"):
|
||||
return ref
|
||||
|
||||
raise cmuxError(f"Unable to resolve focused surface ref in workspace {workspace_id}: {listed}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
cli = _find_cli_binary()
|
||||
|
||||
help_text = _run_cli(cli, ["tab-action", "--help"], json_output=False)
|
||||
_must("Target tab" in help_text, "tab-action --help should describe tab target naming")
|
||||
_must("tab:<n>" in help_text, "tab-action --help should mention tab:<n> refs")
|
||||
_must("--tab tab:" in help_text, "tab-action examples should use tab: refs")
|
||||
|
||||
with cmux(SOCKET_PATH) as c:
|
||||
caps = c.capabilities() or {}
|
||||
methods = set(caps.get("methods") or [])
|
||||
for method in ["workspace.action", "tab.action", "surface.action"]:
|
||||
_must(method in methods, f"Missing method in capabilities: {method}")
|
||||
|
||||
created = c._call("workspace.create", {}) or {}
|
||||
ws_id = str(created.get("workspace_id") or "")
|
||||
_must(bool(ws_id), f"workspace.create returned no workspace_id: {created}")
|
||||
try:
|
||||
c._call("workspace.select", {"workspace_id": ws_id})
|
||||
|
||||
surface_ref = _focused_surface_ref(c, ws_id)
|
||||
tab_ref = "tab:" + surface_ref.split(":", 1)[1]
|
||||
|
||||
pin = _run_cli_json(cli, ["tab-action", "--workspace", ws_id, "--tab", tab_ref, "--action", "pin"])
|
||||
_must(str(pin.get("tab_ref") or "").startswith("tab:"), f"Expected tab_ref in tab-action payload: {pin}")
|
||||
_must(bool(pin.get("pinned")) is True, f"tab-action pin should report pinned=true: {pin}")
|
||||
|
||||
unpin = _run_cli_json(cli, ["tab-action", "--workspace", ws_id, "--tab", tab_ref, "--action", "unpin"])
|
||||
_must(bool(unpin.get("pinned")) is False, f"tab-action unpin should report pinned=false: {unpin}")
|
||||
|
||||
socket_tab = c._call("tab.action", {"workspace_id": ws_id, "tab_id": tab_ref, "action": "clear_name"}) or {}
|
||||
_must(str(socket_tab.get("tab_ref") or "").startswith("tab:"), f"Expected tab_ref in tab.action result: {socket_tab}")
|
||||
_must(str(socket_tab.get("workspace_id") or "") == ws_id, f"tab.action should target requested workspace: {socket_tab}")
|
||||
finally:
|
||||
try:
|
||||
c.close_workspace(ws_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print("PASS: tab/workspace naming stays consistent across tab-action CLI and socket APIs")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue