Remove source-shape regression tests

This commit is contained in:
Lawrence Chen 2026-03-09 17:14:22 -07:00
parent c24ca32614
commit 8b65627750
29 changed files with 0 additions and 3402 deletions

View file

@ -939,52 +939,6 @@ final class RecentlyClosedBrowserStackTests: XCTestCase {
}
}
final class TabManagerNotificationOrderingSourceTests: XCTestCase {
func testGhosttyDidSetTitleObserverDoesNotHopThroughTask() throws {
let projectRoot = findProjectRoot()
let tabManagerURL = projectRoot.appendingPathComponent("Sources/TabManager.swift")
let source = try String(contentsOf: tabManagerURL, encoding: .utf8)
guard let titleObserverStart = source.range(of: "forName: .ghosttyDidSetTitle"),
let focusObserverStart = source.range(
of: "forName: .ghosttyDidFocusSurface",
range: titleObserverStart.upperBound..<source.endIndex
) else {
XCTFail("Failed to locate TabManager notification observer block in Sources/TabManager.swift")
return
}
let block = String(source[titleObserverStart.lowerBound..<focusObserverStart.lowerBound])
XCTAssertFalse(
block.contains("Task {"),
"""
The .ghosttyDidSetTitle observer must update model state in the notification callback.
Using Task can reorder updates and leave titlebar/toolbar one event behind.
"""
)
XCTAssertTrue(
block.contains("MainActor.assumeIsolated"),
"Expected .ghosttyDidSetTitle observer to run synchronously on MainActor."
)
XCTAssertTrue(
block.contains("enqueuePanelTitleUpdate"),
"Expected .ghosttyDidSetTitle observer to enqueue panel title updates."
)
}
private func findProjectRoot() -> URL {
var dir = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent()
for _ in 0..<10 {
let marker = dir.appendingPathComponent("GhosttyTabs.xcodeproj")
if FileManager.default.fileExists(atPath: marker.path) {
return dir
}
dir = dir.deletingLastPathComponent()
}
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
}
}
final class SocketControlSettingsTests: XCTestCase {
func testMigrateModeSupportsExpandedSocketModes() {
XCTAssertEqual(SocketControlSettings.migrateMode("off"), .off)

View file

@ -8,163 +8,6 @@ import AppKit
@testable import cmux
#endif
/// Regression test: ensures UpdatePill is never gated behind #if DEBUG in production code paths.
/// This prevents accidentally hiding the update UI in Release builds.
final class UpdatePillReleaseVisibilityTests: XCTestCase {
/// Source files that must show UpdatePill without #if DEBUG guards.
private let filesToCheck = [
"Sources/Update/UpdateTitlebarAccessory.swift",
"Sources/ContentView.swift",
]
func testUpdatePillNotGatedBehindDebug() throws {
let projectRoot = findProjectRoot()
for relativePath in filesToCheck {
let url = projectRoot.appendingPathComponent(relativePath)
let source = try String(contentsOf: url, encoding: .utf8)
let lines = source.components(separatedBy: .newlines)
// Track #if DEBUG nesting depth.
var debugDepth = 0
for (index, line) in lines.enumerated() {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed == "#if DEBUG" || trimmed.hasPrefix("#if DEBUG ") {
debugDepth += 1
} else if trimmed == "#endif" && debugDepth > 0 {
debugDepth -= 1
} else if trimmed == "#else" && debugDepth > 0 {
// #else inside #if DEBUG means we're in the non-debug branch that's fine.
// But UpdatePill in the #if DEBUG branch (before #else) is the problem.
// We handle this by only flagging UpdatePill when debugDepth > 0 and we haven't
// hit #else yet. For simplicity, treat #else as flipping out of the guarded section.
debugDepth -= 1
}
if debugDepth > 0 && trimmed.contains("UpdatePill") {
XCTFail(
"""
\(relativePath):\(index + 1) UpdatePill is inside #if DEBUG. \
This hides the update UI in Release builds. Remove the #if DEBUG guard \
or move UpdatePill to the #else branch.
"""
)
}
}
}
}
private func findProjectRoot() -> URL {
// Walk up from the test bundle to find the project root (contains GhosttyTabs.xcodeproj).
var dir = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent()
for _ in 0..<10 {
let marker = dir.appendingPathComponent("GhosttyTabs.xcodeproj")
if FileManager.default.fileExists(atPath: marker.path) {
return dir
}
dir = dir.deletingLastPathComponent()
}
// Fallback: assume CWD is project root.
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
}
}
/// Regression test: ensure WKWebView can load HTTP development URLs (e.g. *.localtest.me).
final class AppTransportSecurityTests: XCTestCase {
func testInfoPlistAllowsArbitraryLoadsInWebContent() throws {
let projectRoot = findProjectRoot()
let infoPlistURL = projectRoot.appendingPathComponent("Resources/Info.plist")
let data = try Data(contentsOf: infoPlistURL)
var format = PropertyListSerialization.PropertyListFormat.xml
let plist = try XCTUnwrap(
PropertyListSerialization.propertyList(from: data, options: [], format: &format) as? [String: Any]
)
let ats = try XCTUnwrap(plist["NSAppTransportSecurity"] as? [String: Any])
XCTAssertEqual(
ats["NSAllowsArbitraryLoadsInWebContent"] as? Bool,
true,
"Resources/Info.plist must allow HTTP loads in WKWebView for local dev hostnames."
)
}
private func findProjectRoot() -> URL {
var dir = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent()
for _ in 0..<10 {
let marker = dir.appendingPathComponent("GhosttyTabs.xcodeproj")
if FileManager.default.fileExists(atPath: marker.path) {
return dir
}
dir = dir.deletingLastPathComponent()
}
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
}
}
final class AppBrowserURLSchemeTests: XCTestCase {
func testInfoPlistRegistersHTTPAndHTTPSAsHandledSchemes() throws {
let projectRoot = findProjectRoot()
let infoPlistURL = projectRoot.appendingPathComponent("Resources/Info.plist")
let data = try Data(contentsOf: infoPlistURL)
var format = PropertyListSerialization.PropertyListFormat.xml
let plist = try XCTUnwrap(
PropertyListSerialization.propertyList(from: data, options: [], format: &format) as? [String: Any]
)
let urlTypes = try XCTUnwrap(plist["CFBundleURLTypes"] as? [[String: Any]])
let schemes = Set(
urlTypes
.flatMap { $0["CFBundleURLSchemes"] as? [String] ?? [] }
.map { $0.lowercased() }
)
XCTAssertTrue(
schemes.contains("http"),
"Resources/Info.plist must register the http URL scheme so macOS can treat cmux as a browser candidate."
)
XCTAssertTrue(
schemes.contains("https"),
"Resources/Info.plist must register the https URL scheme so macOS can treat cmux as a browser candidate."
)
let browserURLTypes = urlTypes.filter { urlType in
let urlSchemes = Set((urlType["CFBundleURLSchemes"] as? [String] ?? []).map { $0.lowercased() })
return !urlSchemes.isDisjoint(with: ["http", "https"])
}
XCTAssertFalse(
browserURLTypes.isEmpty,
"Resources/Info.plist must include a browser URL type entry for http and https."
)
for urlType in browserURLTypes {
XCTAssertEqual(
urlType["CFBundleTypeRole"] as? String,
"Viewer",
"Browser URL schemes should be declared with the Viewer role."
)
XCTAssertEqual(
urlType["LSHandlerRank"] as? String,
"Default",
"Browser URL schemes should advertise a default-handler rank."
)
}
}
private func findProjectRoot() -> URL {
var dir = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent()
for _ in 0..<10 {
let marker = dir.appendingPathComponent("GhosttyTabs.xcodeproj")
if FileManager.default.fileExists(atPath: marker.path) {
return dir
}
dir = dir.deletingLastPathComponent()
}
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
}
}
final class BrowserInsecureHTTPSettingsTests: XCTestCase {
func testDefaultAllowlistPatternsArePresent() {
XCTAssertEqual(
@ -334,45 +177,3 @@ final class TitlebarControlsHoverPolicyTests: XCTestCase {
XCTAssertFalse(titlebarControlsShouldTrackButtonHover(config: TitlebarControlsStyle.softButtons.config))
}
}
/// Regression test: ensure new terminal windows are born in full-size content mode so
/// titlebar/content offsets are correct before the first resize.
final class MainWindowLayoutStyleTests: XCTestCase {
func testCreateMainWindowUsesFullSizeContentViewStyleMask() throws {
let projectRoot = findProjectRoot()
let appDelegateURL = projectRoot.appendingPathComponent("Sources/AppDelegate.swift")
let source = try String(contentsOf: appDelegateURL, encoding: .utf8)
guard let start = source.range(of: "func createMainWindow("),
let end = source.range(of: "@objc func checkForUpdates", range: start.upperBound..<source.endIndex) else {
XCTFail("Could not locate createMainWindow block in Sources/AppDelegate.swift")
return
}
let block = String(source[start.lowerBound..<end.lowerBound])
let regex = try NSRegularExpression(
pattern: #"styleMask:\s*\[[^\]]*\.fullSizeContentView"#,
options: [.dotMatchesLineSeparators]
)
let range = NSRange(block.startIndex..<block.endIndex, in: block)
XCTAssertNotNil(
regex.firstMatch(in: block, options: [], range: range),
"""
createMainWindow must include `.fullSizeContentView` in the NSWindow style mask.
Without it, initial titlebar/content offsets can be wrong until a manual resize.
"""
)
}
private func findProjectRoot() -> URL {
var dir = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent()
for _ in 0..<10 {
let marker = dir.appendingPathComponent("GhosttyTabs.xcodeproj")
if FileManager.default.fileExists(atPath: marker.path) {
return dir
}
dir = dir.deletingLastPathComponent()
}
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
}
}

View file

@ -1,126 +0,0 @@
#!/usr/bin/env python3
"""Static regression guards for browser chrome contrast in mixed theme setups."""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift"
source = view_path.read_text(encoding="utf-8")
failures: list[str] = []
try:
browser_panel_view_block = extract_block(source, "struct BrowserPanelView: View")
except ValueError as error:
failures.append(str(error))
browser_panel_view_block = ""
try:
resolver_block = extract_block(source, "func resolvedBrowserChromeColorScheme(")
except ValueError as error:
failures.append(str(error))
resolver_block = ""
if resolver_block:
if "backgroundColor.isLightColor ? .light : .dark" not in resolver_block:
failures.append(
"resolvedBrowserChromeColorScheme must map luminance to a light/dark ColorScheme"
)
try:
chrome_scheme_block = extract_block(
browser_panel_view_block,
"private var browserChromeColorScheme: ColorScheme",
)
except ValueError as error:
failures.append(str(error))
chrome_scheme_block = ""
if chrome_scheme_block and "resolvedBrowserChromeColorScheme(" not in chrome_scheme_block:
failures.append("browserChromeColorScheme must use resolvedBrowserChromeColorScheme")
try:
omnibar_background_block = extract_block(
browser_panel_view_block,
"private var omnibarPillBackgroundColor: NSColor",
)
except ValueError as error:
failures.append(str(error))
omnibar_background_block = ""
if omnibar_background_block and "for: browserChromeColorScheme" not in omnibar_background_block:
failures.append("omnibar pill background must use browserChromeColorScheme")
try:
address_bar_block = extract_block(
browser_panel_view_block,
"private var addressBar: some View",
)
except ValueError as error:
failures.append(str(error))
address_bar_block = ""
if address_bar_block and ".environment(\\.colorScheme, browserChromeColorScheme)" not in address_bar_block:
failures.append("addressBar must apply browserChromeColorScheme via environment")
try:
body_block = extract_block(browser_panel_view_block, "var body: some View")
except ValueError as error:
failures.append(str(error))
body_block = ""
if body_block:
if "OmnibarSuggestionsView(" not in body_block:
failures.append("Expected OmnibarSuggestionsView block in BrowserPanelView body")
elif ".environment(\\.colorScheme, browserChromeColorScheme)" not in body_block:
failures.append("Omnibar suggestions must apply browserChromeColorScheme via environment")
if failures:
print("FAIL: browser chrome contrast regression guards failed")
for failure in failures:
print(f" - {failure}")
return 1
print("PASS: browser chrome contrast regression guards are in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,86 +0,0 @@
#!/usr/bin/env python3
"""Static regression guard for browser console/errors CLI output formatting.
Ensures non-JSON `browser console list` and `browser errors list` do not fall
back to unconditional `OK` when logs exist.
"""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
failures: list[str] = []
cli_path = root / "CLI" / "cmux.swift"
cli_source = cli_path.read_text(encoding="utf-8")
browser_block = extract_block(cli_source, "private func runBrowserCommand(")
if "func displayBrowserLogItems(_ value: Any?) -> String?" not in browser_block:
failures.append("runBrowserCommand() is missing displayBrowserLogItems() helper")
else:
helper_block = extract_block(browser_block, "func displayBrowserLogItems(_ value: Any?) -> String?")
if "return \"[\\(level)] \\(text)\"" not in helper_block:
failures.append("displayBrowserLogItems() no longer renders level-prefixed log lines")
if "return \"[error] \\(message)\"" not in helper_block:
failures.append("displayBrowserLogItems() no longer renders concise JS error messages")
if "return displayBrowserValue(dict)" not in helper_block:
failures.append("displayBrowserLogItems() no longer falls back to structured formatting")
console_block = extract_block(browser_block, 'if subcommand == "console"')
if 'displayBrowserLogItems(payload["entries"])' not in console_block:
failures.append("browser console path no longer formats entries for non-JSON output")
if 'output(payload, fallback: "OK")' in console_block:
failures.append("browser console path regressed to unconditional OK output")
errors_block = extract_block(browser_block, 'if subcommand == "errors"')
if 'displayBrowserLogItems(payload["errors"])' not in errors_block:
failures.append("browser errors path no longer formats errors for non-JSON output")
if 'output(payload, fallback: "OK")' in errors_block:
failures.append("browser errors path regressed to unconditional OK output")
if failures:
print("FAIL: browser console/errors CLI output regression guard failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: browser console/errors CLI output regression guard is in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""Static regression checks for browser DevTools/portal review fixes.
Guards two follow-up fixes:
1) DevTools toggle path must retry restore when inspector show is transiently ignored.
2) Browser portal visibility must propagate even if host is temporarily off-window.
"""
from __future__ import annotations
import re
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
failures: list[str] = []
panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift"
panel_source = panel_path.read_text(encoding="utf-8")
toggle_block = extract_block(panel_source, "func toggleDeveloperTools() -> Bool")
if "visibleAfterToggle" not in toggle_block:
failures.append("toggleDeveloperTools() no longer re-checks inspector visibility")
if "scheduleDeveloperToolsRestoreRetry()" not in toggle_block:
failures.append("toggleDeveloperTools() no longer schedules a DevTools restore retry")
view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift"
view_source = view_path.read_text(encoding="utf-8")
portal_update_block = extract_block(view_source, "private func updateUsingWindowPortal(")
if "BrowserWindowPortalRegistry.updateEntryVisibility(" not in portal_update_block:
failures.append("BrowserPanelView.updateUsingWindowPortal() is missing deferred portal visibility propagation")
if "zPriority: coordinator.desiredPortalZPriority" not in portal_update_block:
failures.append("BrowserPanelView deferred portal update no longer propagates zPriority")
portal_path = root / "Sources" / "BrowserWindowPortal.swift"
portal_source = portal_path.read_text(encoding="utf-8")
if not re.search(
r"func\s+updateEntryVisibility\s*\(\s*forWebViewId\s+webViewId:\s*ObjectIdentifier,\s*visibleInUI:\s*Bool,\s*zPriority:\s*Int\s*\)",
portal_source,
flags=re.MULTILINE,
):
failures.append("WindowBrowserPortal is missing updateEntryVisibility(forWebViewId:visibleInUI:zPriority:)")
if not re.search(
r"static\s+func\s+updateEntryVisibility\s*\(\s*for\s+webView:\s*WKWebView,\s*visibleInUI:\s*Bool,\s*zPriority:\s*Int\s*\)",
portal_source,
flags=re.MULTILINE,
):
failures.append("BrowserWindowPortalRegistry is missing updateEntryVisibility(for:visibleInUI:zPriority:)")
if failures:
print("FAIL: browser devtools/portal regression guards failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: browser devtools/portal regression guards are in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,128 +0,0 @@
#!/usr/bin/env python3
"""Static regression guard for browser eval async wrapping + telemetry injection."""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def extract_span(source: str, start_marker: str, end_marker: str) -> str:
start = source.find(start_marker)
if start < 0:
raise ValueError(f"Missing start marker: {start_marker}")
end = source.find(end_marker, start)
if end < 0:
raise ValueError(f"Missing end marker: {end_marker}")
return source[start:end]
def main() -> int:
root = repo_root()
failures: list[str] = []
terminal_path = root / "Sources" / "TerminalController.swift"
panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift"
terminal_source = terminal_path.read_text(encoding="utf-8")
panel_source = panel_path.read_text(encoding="utf-8")
if "preferAsync: Bool = false" not in terminal_source:
failures.append("v2RunJavaScript() no longer exposes preferAsync toggle")
run_js_block = extract_block(terminal_source, "private func v2RunJavaScript(")
if "callAsyncJavaScript" not in run_js_block:
failures.append("v2RunJavaScript() no longer uses callAsyncJavaScript for async JS")
run_browser_js_block = extract_block(terminal_source, "private func v2RunBrowserJavaScript(")
required_wrapper_tokens = [
"let asyncFunctionBody =",
"__cmuxMaybeAwait",
"__cmux_t",
"__cmux_v",
"return await __cmuxEvalInFrame();",
"preferAsync: true",
]
for token in required_wrapper_tokens:
if token not in run_browser_js_block:
failures.append(f"v2RunBrowserJavaScript() missing async eval wrapper token: {token}")
if "v2BrowserUndefinedSentinel" not in terminal_source:
failures.append("TerminalController is missing undefined sentinel handling")
if "v2BrowserEvalEnvelopeTypeUndefined" not in terminal_source:
failures.append("TerminalController is missing undefined envelope decode constant")
hook_block = extract_block(terminal_source, "private func v2BrowserEnsureTelemetryHooks(")
if "BrowserPanel.telemetryHookBootstrapScriptSource" not in hook_block:
failures.append("v2BrowserEnsureTelemetryHooks() no longer uses shared BrowserPanel telemetry source")
if "static let telemetryHookBootstrapScriptSource" not in panel_source:
failures.append("BrowserPanel is missing telemetryHookBootstrapScriptSource")
if "static let dialogTelemetryHookBootstrapScriptSource" not in panel_source:
failures.append("BrowserPanel is missing dialogTelemetryHookBootstrapScriptSource")
base_script_span = extract_span(
panel_source,
"static let telemetryHookBootstrapScriptSource =",
"static let dialogTelemetryHookBootstrapScriptSource =",
)
if "window.alert = function(message)" in base_script_span:
failures.append("Document-start telemetry script should not override alert dialogs")
if "window.confirm = function(message)" in base_script_span:
failures.append("Document-start telemetry script should not override confirm dialogs")
if "window.prompt = function(message, defaultValue)" in base_script_span:
failures.append("Document-start telemetry script should not override prompt dialogs")
panel_init_block = extract_block(
panel_source,
"init(workspaceId: UUID, initialURL: URL? = nil, bypassInsecureHTTPHostOnce: String? = nil)",
)
required_init_tokens = [
"config.userContentController.addUserScript(",
"source: Self.telemetryHookBootstrapScriptSource",
"injectionTime: .atDocumentStart",
]
for token in required_init_tokens:
if token not in panel_init_block:
failures.append(f"BrowserPanel init() missing telemetry user-script token: {token}")
if failures:
print("FAIL: browser eval async wrapper / telemetry injection regression guard failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: browser eval async wrapper / telemetry injection regression guard is in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,89 +0,0 @@
#!/usr/bin/env python3
"""Static regression guard for browser eval CLI output formatting.
Ensures `cmux browser <surface> eval <script>` prints the evaluated value
instead of always printing `OK`.
"""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
failures: list[str] = []
cli_path = root / "CLI" / "cmux.swift"
cli_source = cli_path.read_text(encoding="utf-8")
browser_block = extract_block(cli_source, "private func runBrowserCommand(")
if "func displayBrowserValue(_ value: Any) -> String" not in browser_block:
failures.append("runBrowserCommand() is missing displayBrowserValue() helper")
else:
value_block = extract_block(browser_block, "func displayBrowserValue(_ value: Any) -> String")
if 'dict["__cmux_t"] as? String' not in value_block or 'type == "undefined"' not in value_block:
failures.append("displayBrowserValue() no longer maps __cmux_t=undefined to literal 'undefined'")
required_guards = [
"if value is NSNull",
"if let string = value as? String",
"if let bool = value as? Bool",
"if let number = value as? NSNumber",
]
for guard in required_guards:
if guard not in value_block:
failures.append(f"displayBrowserValue() no longer handles: {guard}")
eval_block = extract_block(browser_block, 'if subcommand == "eval"')
if 'let payload = try client.sendV2(method: "browser.eval"' not in eval_block:
failures.append("browser eval path no longer calls browser.eval v2 method")
if 'if let value = payload["value"]' not in eval_block:
failures.append("browser eval path no longer reads payload value")
if "fallback = displayBrowserValue(value)" not in eval_block:
failures.append("browser eval path no longer formats payload value for CLI output")
if 'output(payload, fallback: "OK")' in eval_block:
failures.append("browser eval path regressed to unconditional OK output")
if failures:
print("FAIL: browser eval CLI output regression guard failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: browser eval CLI output regression guard is in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,84 +0,0 @@
#!/usr/bin/env python3
"""Static regression checks for favicon sync during browser navigation.
Guards the race fix where stale async favicon fetches must not overwrite the
icon after the user navigates (including back/forward and same-URL reloads),
while still allowing same-document URL changes (pushState/hash updates).
"""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
failures: list[str] = []
panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift"
panel_source = panel_path.read_text(encoding="utf-8")
if "private var faviconRefreshGeneration: Int = 0" not in panel_source:
failures.append("BrowserPanel is missing faviconRefreshGeneration state")
refresh_block = extract_block(panel_source, "private func refreshFavicon(from webView: WKWebView)")
if refresh_block.count("isCurrentFaviconRefresh(") < 3:
failures.append("refreshFavicon() no longer checks staleness at each async stage")
current_guard_block = extract_block(panel_source, "private func isCurrentFaviconRefresh(")
if "generation == faviconRefreshGeneration" not in current_guard_block:
failures.append("isCurrentFaviconRefresh() no longer validates refresh generation")
if "webView.url?.absoluteString == pageURLString" in current_guard_block:
failures.append("isCurrentFaviconRefresh() still blocks same-document history URL changes")
loading_block = extract_block(panel_source, "private func handleWebViewLoadingChanged(_ newValue: Bool)")
if "faviconRefreshGeneration &+= 1" not in loading_block:
failures.append("handleWebViewLoadingChanged() no longer invalidates old favicon refreshes")
if "faviconTask?.cancel()" not in loading_block:
failures.append("handleWebViewLoadingChanged() no longer cancels stale favicon tasks")
if "lastFaviconURLString = nil" not in loading_block:
failures.append("handleWebViewLoadingChanged() no longer resets favicon URL cache on new loads")
if failures:
print("FAIL: browser favicon navigation regression guard failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: browser favicon navigation guard is in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,203 +0,0 @@
#!/usr/bin/env python3
"""Regression guards for browser Cmd+F overlay layering in portal mode."""
from __future__ import annotations
from regression_helpers import extract_block, repo_root
def main() -> int:
root = repo_root()
view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift"
panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift"
overlay_path = root / "Sources" / "Find" / "BrowserSearchOverlay.swift"
source = view_path.read_text(encoding="utf-8")
panel_source = panel_path.read_text(encoding="utf-8")
overlay_source = overlay_path.read_text(encoding="utf-8")
failures: list[str] = []
try:
browser_panel_view_block = extract_block(
source, "struct BrowserPanelView: View"
)
except ValueError as error:
failures.append(str(error))
browser_panel_view_block = ""
try:
body_block = extract_block(browser_panel_view_block, "var body: some View")
except ValueError as error:
failures.append(str(error))
body_block = ""
fallback_signature = (
"if !panel.shouldRenderWebView, let searchState = panel.searchState {"
)
fallback_block = ""
if body_block:
try:
fallback_block = extract_block(body_block, fallback_signature)
except ValueError:
failures.append(
"BrowserPanelView must provide BrowserSearchOverlay fallback for new-tab state "
"(when WKWebView is not mounted)"
)
if fallback_block and "BrowserSearchOverlay(" not in fallback_block:
failures.append(
"BrowserPanelView fallback branch must mount BrowserSearchOverlay for new-tab state"
)
try:
webview_repr_block = extract_block(
source, "struct WebViewRepresentable: NSViewRepresentable"
)
except ValueError as error:
failures.append(str(error))
webview_repr_block = ""
if webview_repr_block:
if "let browserSearchState: BrowserSearchState?" not in webview_repr_block:
failures.append(
"WebViewRepresentable must include browserSearchState so Cmd+F state changes trigger updates"
)
if (
"var searchOverlayHostingView: NSHostingView<BrowserSearchOverlay>?"
not in webview_repr_block
):
failures.append(
"WebViewRepresentable.Coordinator must own a BrowserSearchOverlay hosting view"
)
if "private static func updateSearchOverlay(" not in webview_repr_block:
failures.append(
"WebViewRepresentable must define updateSearchOverlay helper"
)
if "containerView: webView.superview" not in webview_repr_block:
failures.append(
"Portal updates must sync BrowserSearchOverlay against the web view container"
)
if "removeSearchOverlay(from: coordinator)" not in webview_repr_block:
failures.append(
"WebViewRepresentable must remove browser search overlays during teardown/rebind"
)
if "browserSearchState: panel.searchState" not in source:
failures.append(
"BrowserPanelView must pass panel.searchState into WebViewRepresentable"
)
try:
update_ns_view_block = extract_block(
webview_repr_block, "func updateNSView(_ nsView: NSView, context: Context)"
)
except ValueError as error:
failures.append(str(error))
update_ns_view_block = ""
if "updateSearchOverlay(" in update_ns_view_block:
failures.append(
"updateNSView must not re-run updateSearchOverlay outside portal lifecycle paths"
)
try:
suppress_focus_block = extract_block(
panel_source, "func shouldSuppressWebViewFocus() -> Bool"
)
except ValueError as error:
failures.append(str(error))
suppress_focus_block = ""
if "if searchState != nil {" not in suppress_focus_block:
failures.append(
"BrowserPanel.shouldSuppressWebViewFocus must suppress focus while find-in-page is active"
)
try:
start_find_block = extract_block(panel_source, "func startFind()")
except ValueError as error:
failures.append(str(error))
start_find_block = ""
if start_find_block:
if "postBrowserSearchFocusNotification()" not in start_find_block:
failures.append(
"BrowserPanel.startFind must publish browserSearchFocus notifications"
)
if "DispatchQueue.main.async {" not in start_find_block:
failures.append(
"BrowserPanel.startFind must re-post focus on next runloop to avoid mount races"
)
if "DispatchQueue.main.asyncAfter" not in start_find_block:
failures.append(
"BrowserPanel.startFind must re-post focus shortly after to avoid portal mount races"
)
try:
init_block = extract_block(panel_source, "init(workspaceId: UUID")
except ValueError as error:
failures.append(str(error))
init_block = ""
if init_block:
if (
"self?.searchState = nil" in init_block
or "self.searchState = nil" in init_block
):
failures.append(
"BrowserPanel navigation callbacks must not clear searchState entirely to avoid losing find bar focus"
)
if "restoreFindStateAfterNavigation(replaySearch: true)" not in init_block:
failures.append(
"BrowserPanel.didFinish must preserve find state and replay search on the new page"
)
if "restoreFindStateAfterNavigation(replaySearch: false)" not in init_block:
failures.append(
"BrowserPanel.didFailNavigation must preserve find state without replaying search"
)
try:
restore_find_state_block = extract_block(
panel_source, "private func restoreFindStateAfterNavigation(replaySearch: Bool)"
)
except ValueError as error:
failures.append(str(error))
restore_find_state_block = ""
if restore_find_state_block:
if "state.total = nil" not in restore_find_state_block:
failures.append(
"BrowserPanel restoreFindStateAfterNavigation must clear stale find total count"
)
if "state.selected = nil" not in restore_find_state_block:
failures.append(
"BrowserPanel restoreFindStateAfterNavigation must clear stale selected match"
)
if "if replaySearch, !state.needle.isEmpty {" not in restore_find_state_block:
failures.append(
"BrowserPanel restoreFindStateAfterNavigation must only replay search for successful navigations"
)
if "postBrowserSearchFocusNotification()" not in restore_find_state_block:
failures.append(
"BrowserPanel restoreFindStateAfterNavigation must reassert find field focus"
)
if "private func requestSearchFieldFocus(" not in overlay_source:
failures.append(
"BrowserSearchOverlay must define requestSearchFieldFocus retry helper"
)
if "requestSearchFieldFocus()" not in overlay_source:
failures.append(
"BrowserSearchOverlay must request text focus from appear/notification paths"
)
if failures:
print("FAIL: browser find overlay portal regression guards failed")
for failure in failures:
print(f" - {failure}")
return 1
print("PASS: browser find overlay remains mounted in portal-hosted AppKit layer")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,125 +0,0 @@
#!/usr/bin/env python3
"""Static regression guards for compact browser omnibar sizing."""
from __future__ import annotations
import re
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def parse_cgfloat_constant(source: str, name: str) -> float | None:
match = re.search(
rf"private let {re.escape(name)}: CGFloat = ([0-9]+(?:\.[0-9]+)?)",
source,
)
if not match:
return None
return float(match.group(1))
def main() -> int:
root = repo_root()
failures: list[str] = []
view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift"
view_source = view_path.read_text(encoding="utf-8")
hit_size = parse_cgfloat_constant(view_source, "addressBarButtonHitSize")
if hit_size is None:
failures.append("addressBarButtonHitSize constant is missing")
elif hit_size > 26:
failures.append(
f"addressBarButtonHitSize regressed to {hit_size:g}; expected <= 26 for compact omnibar height"
)
vertical_padding = parse_cgfloat_constant(view_source, "addressBarVerticalPadding")
if vertical_padding is None:
failures.append("addressBarVerticalPadding constant is missing")
elif vertical_padding > 4:
failures.append(
f"addressBarVerticalPadding regressed to {vertical_padding:g}; expected <= 4 for compact omnibar height"
)
omnibar_corner_radius = parse_cgfloat_constant(view_source, "omnibarPillCornerRadius")
if omnibar_corner_radius is None:
failures.append("omnibarPillCornerRadius constant is missing")
elif omnibar_corner_radius > 10:
failures.append(
f"omnibarPillCornerRadius regressed to {omnibar_corner_radius:g}; expected <= 10 to keep a squircle profile"
)
address_bar_block = extract_block(view_source, "private var addressBar: some View")
if ".padding(.vertical, addressBarVerticalPadding)" not in address_bar_block:
failures.append("addressBar no longer applies compact vertical padding via addressBarVerticalPadding")
omnibar_field_block = extract_block(view_source, "private var omnibarField: some View")
if omnibar_field_block.count(
"RoundedRectangle(cornerRadius: omnibarPillCornerRadius, style: .continuous)"
) < 2:
failures.append(
"omnibarField no longer uses continuous rounded-rectangle background+stroke tied to omnibarPillCornerRadius"
)
button_bar_block = extract_block(view_source, "private var addressBarButtonBar: some View")
hit_frame_uses = button_bar_block.count("addressBarButtonHitSize")
if hit_frame_uses < 3:
failures.append(
"navigation buttons no longer consistently use addressBarButtonHitSize frames (padding may be lost)"
)
extract_block(view_source, "private struct OmnibarAddressButtonStyle: ButtonStyle")
style_body_block = extract_block(view_source, "private struct OmnibarAddressButtonStyleBody: View")
if "configuration.isPressed" not in style_body_block:
failures.append("OmnibarAddressButtonStyleBody is missing pressed-state styling")
if "isHovered" not in style_body_block or ".onHover" not in style_body_block:
failures.append("OmnibarAddressButtonStyleBody is missing hover-state styling")
style_uses = view_source.count(".buttonStyle(OmnibarAddressButtonStyle())")
if style_uses < 4:
failures.append(
"address bar buttons no longer consistently use OmnibarAddressButtonStyle"
)
if failures:
print("FAIL: browser omnibar compact layout regression guards failed")
for failure in failures:
print(f" - {failure}")
return 1
print("PASS: browser omnibar compact layout regression guards are in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,94 +0,0 @@
#!/usr/bin/env python3
"""Static regression checks for deterministic browser lifecycle architecture.
Guards the long-term browser mounting design:
1) BrowserPanelView updateNSView must use a single portal-based mount path.
2) Legacy attach-retry and direct attach/detach churn helpers stay removed.
3) BrowserPanel handles WebContent termination via deterministic webview replacement,
not blind `webView.reload()`.
"""
from __future__ import annotations
import subprocess
import shutil
from pathlib import Path
def repo_root() -> Path:
git_path = shutil.which("git")
git_command = git_path if git_path else "git"
result = subprocess.run(
[git_command, "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
failures: list[str] = []
view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift"
view_source = view_path.read_text(encoding="utf-8")
if "updateUsingWindowPortal(nsView, context: context, webView: webView)" not in view_source:
failures.append("updateNSView no longer routes through updateUsingWindowPortal")
if "scheduleAttachRetry(" in view_source:
failures.append("Legacy attach retry helper still present in BrowserPanelView")
if "attachRetryWorkItem" in view_source:
failures.append("Legacy attachRetryWorkItem state still present in BrowserPanelView")
if "usesWindowPortal" in view_source:
failures.append("Dual portal/non-portal lifecycle state still present in BrowserPanelView")
panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift"
panel_source = panel_path.read_text(encoding="utf-8")
if "@Published private(set) var webViewInstanceID" not in panel_source:
failures.append("BrowserPanel is missing webViewInstanceID for deterministic instance remounting")
if "replaceWebViewAfterContentProcessTermination" not in panel_source:
failures.append("BrowserPanel is missing deterministic WebContent termination replacement path")
terminate_delegate = extract_block(
panel_source,
"func webViewWebContentProcessDidTerminate(_ webView: WKWebView)",
)
if "didTerminateWebContentProcess?(webView)" not in terminate_delegate:
failures.append("webContentProcessDidTerminate no longer delegates to deterministic replacement handler")
if "webView.reload()" in terminate_delegate:
failures.append("webContentProcessDidTerminate still does blind webView.reload()")
if failures:
print("FAIL: browser lifecycle architecture regression guards failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: browser lifecycle architecture regression guards are in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,115 +0,0 @@
#!/usr/bin/env python3
"""Regression test: CLI socket Sentry telemetry must apply to all commands."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def reject(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
cli_path = repo_root / "CLI" / "cmux.swift"
if not cli_path.exists():
print(f"FAIL: missing expected file: {cli_path}")
return 1
content = cli_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
content,
"private final class CLISocketSentryTelemetry {",
"Missing CLISocketSentryTelemetry definition",
failures,
)
require(
content,
'processEnv["CMUX_CLI_SENTRY_DISABLED"] == "1" ||',
"Missing CMUX_CLI_SENTRY_DISABLED kill switch",
failures,
)
require(
content,
'processEnv["CMUX_CLAUDE_HOOK_SENTRY_DISABLED"] == "1"',
"Missing backwards-compatible CMUX_CLAUDE_HOOK_SENTRY_DISABLED kill switch",
failures,
)
require(
content,
"private var shouldEmit: Bool {\n !disabledByEnv\n }",
"Telemetry scope should be command-agnostic (only disabled by env kill switch)",
failures,
)
require(
content,
'let crumb = Breadcrumb(level: .info, category: "cmux.cli")',
"Telemetry breadcrumb category should be cmux.cli",
failures,
)
require(
content,
'"command": command,',
"Base telemetry context must include command name",
failures,
)
require(
content,
"let cliTelemetry = CLISocketSentryTelemetry(",
"CLI should initialize generic socket telemetry",
failures,
)
require(
content,
'cliTelemetry.breadcrumb(\n "socket.connect.attempt",',
"CLI should emit socket.connect.attempt breadcrumb for commands",
failures,
)
reject(
content,
"self.enabled = command == \"claude-hook\"",
"Telemetry regressed to claude-hook-only scope",
failures,
)
reject(
content,
"enabled && !disabledByEnv",
"Telemetry still depends on legacy enabled flag",
failures,
)
if failures:
print("FAIL: CLI socket telemetry scope regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: CLI socket telemetry scope is command-agnostic")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,147 +0,0 @@
#!/usr/bin/env python3
"""Regression tests for CLI subcommand help coverage and accuracy."""
from __future__ import annotations
import re
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def extract_switch_commands(content: str, start_index: int = 0) -> tuple[set[str], int]:
marker = "switch command {"
marker_index = content.find(marker, start_index)
if marker_index == -1:
return set(), -1
open_brace = content.find("{", marker_index)
if open_brace == -1:
return set(), -1
depth = 1
cursor = open_brace + 1
while cursor < len(content) and depth > 0:
char = content[cursor]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
cursor += 1
block = content[open_brace + 1:cursor - 1]
commands: set[str] = set()
collecting_case = False
case_lines: list[str] = []
for line in block.splitlines():
stripped = line.strip()
if stripped.startswith("case "):
collecting_case = True
case_lines = [line]
elif collecting_case:
case_lines.append(line)
if collecting_case and ":" in line:
case_text = "\n".join(case_lines)
commands.update(re.findall(r'"([^"]+)"', case_text))
collecting_case = False
case_lines = []
return commands, cursor
def main() -> int:
repo_root = get_repo_root()
cli_path = repo_root / "CLI" / "cmux.swift"
if not cli_path.exists():
print(f"FAIL: missing expected file: {cli_path}")
return 1
content = cli_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
content,
'if commandArgs.contains("--help") || commandArgs.contains("-h") {',
"Subcommand help pre-dispatch gate is missing",
failures,
)
require(
content,
'if dispatchSubcommandHelp(command: command, commandArgs: commandArgs) {',
"Subcommand help dispatch call is missing",
failures,
)
require(
content,
"print(\"Unknown command '\\(command)'. Run 'cmux help' to see available commands.\")",
"Subcommand help fallback unknown-command line is missing",
failures,
)
require(
content,
"print(\"Unknown command '\\(command)'. Run 'cmux help' to see available commands.\")\n return",
"Subcommand help fallback must return before command execution",
failures,
)
dispatch_commands, next_index = extract_switch_commands(content, 0)
subcommand_usage_commands, _ = extract_switch_commands(content, next_index if next_index != -1 else 0)
if not dispatch_commands:
failures.append("Failed to parse main dispatch switch command list")
if not subcommand_usage_commands:
failures.append("Failed to parse subcommandUsage switch command list")
missing_help_entries = sorted(dispatch_commands - subcommand_usage_commands)
if missing_help_entries:
failures.append(
"Missing subcommandUsage entries for dispatch command(s): "
+ ", ".join(missing_help_entries)
)
# Regression checks for concrete help text that previously drifted from dispatch logic.
for needle, message in [
('case "help":', "Missing subcommandUsage entry for help"),
("Usage: cmux help", "help subcommand usage text is missing"),
("Usage: cmux move-workspace-to-window --workspace <id|ref|index> --window <id|ref|index>", "move-workspace-to-window help must document index handles"),
("--tab <id|ref|index> Target tab (accepts tab:<n> or surface:<n>; default: $CMUX_TAB_ID, then $CMUX_SURFACE_ID, then focused tab)", "tab-action help must document CMUX_TAB_ID/CMUX_SURFACE_ID fallback"),
("--workspace <id|ref|index> Workspace to rename (default: current/$CMUX_WORKSPACE_ID)", "rename-workspace help must document CMUX_WORKSPACE_ID fallback"),
("text|html|value|count|box|styles|attr: [--selector <css> | <css>]", "browser get help must document --selector"),
("attr: [--attr <name> | <name>]", "browser get attr help must document --attr"),
("styles: [--property <name>]", "browser get styles help must document --property"),
("role: [--name <text>] [--exact] <role>", "browser find role help must document --name/--exact"),
("text|label|placeholder|alt|title|testid: [--exact] <text>", "browser find text-like help must document --exact"),
("nth: [--index <n> | <n>] [--selector <css> | <css>]", "browser find nth help must document --index/--selector"),
("route <pattern> [--abort] [--body <text>]", "browser network route help must document --abort/--body"),
]:
require(content, needle, message, failures)
if failures:
print("FAIL: CLI subcommand help regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: CLI subcommand help coverage and flag/env documentation are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,149 +0,0 @@
#!/usr/bin/env python3
"""Regression test: `cmux tree` command wiring and output contract."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
cli_path = repo_root / "CLI" / "cmux.swift"
controller_path = repo_root / "Sources" / "TerminalController.swift"
if not cli_path.exists():
print(f"FAIL: missing expected file: {cli_path}")
return 1
if not controller_path.exists():
print(f"FAIL: missing expected file: {controller_path}")
return 1
content = cli_path.read_text(encoding="utf-8")
controller_content = controller_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
content,
'case "tree":\n try runTreeCommand(commandArgs: commandArgs, client: client, jsonOutput: jsonOutput, idFormat: idFormat)',
"Missing `tree` command dispatch",
failures,
)
require(
content,
"tree [--all] [--workspace <id|ref|index>]",
"Top-level usage text missing tree command",
failures,
)
require(
content,
"Usage: cmux tree [flags]",
"Subcommand help for `cmux tree --help` is missing",
failures,
)
require(
content,
"Known flags: --all --workspace <id|ref|index> --json",
"Tree flag validation for --all/--workspace is missing",
failures,
)
require(
content,
"--json Structured JSON output",
"Tree help text should document --json",
failures,
)
require(
content,
'print(jsonString(formatIDs(payload, mode: idFormat)))',
"Tree command JSON output should honor --id-format conversion",
failures,
)
# Data sources needed for full hierarchy + browser URLs.
for method in [
'method: "system.tree"',
'method: "system.identify"',
'method: "window.list"',
'method: "workspace.list"',
'method: "pane.list"',
'method: "surface.list"',
'method: "browser.tab.list"',
'method: "browser.url.get"',
]:
require(
content,
method,
f"Tree command is missing expected API call: {method}",
failures,
)
# Text tree rendering contract.
for glyph in ['"├── "', '"└── "', '""']:
require(
content,
glyph,
f"Tree output missing box-drawing glyph: {glyph}",
failures,
)
for marker in ["[current]", "[selected]", "[focused]", "◀ active", "◀ here"]:
require(
content,
marker,
f"Tree output missing required marker: {marker}",
failures,
)
require(
content,
'surfaceType.lowercased() == "browser"',
"Tree surface rendering should special-case browser surfaces",
failures,
)
require(
content,
'let url = surface["url"] as? String',
"Tree surface rendering should include browser URL when available",
failures,
)
# Server-side one-shot hierarchy path for performance.
for needle, message in [
('case "system.tree":', "Socket router is missing system.tree dispatch"),
('"system.tree"', "Capabilities list should advertise system.tree"),
("private func v2SystemTree(params: [String: Any]) -> V2CallResult {", "Missing v2SystemTree implementation"),
('"active":', "system.tree payload should include focused path"),
('"caller":', "system.tree payload should include caller path"),
('"windows":', "system.tree payload should include hierarchy windows"),
]:
require(controller_content, needle, message, failures)
if failures:
print("FAIL: cmux tree command regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: cmux tree command wiring and output contract are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,85 +0,0 @@
#!/usr/bin/env python3
"""Regression test: CLI version output wiring keeps commit metadata support."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
cli_path = repo_root / "CLI" / "cmux.swift"
if not cli_path.exists():
print(f"FAIL: missing expected file: {cli_path}")
return 1
content = cli_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
content,
'let commit = info["CMUXCommit"].flatMap { normalizedCommitHash($0) }',
"versionSummary no longer reads CMUXCommit metadata",
failures,
)
require(
content,
'return "\\(baseSummary) [\\(commit)]"',
"versionSummary no longer appends commit metadata",
failures,
)
require(
content,
'if let commit = dictionary["CMUXCommit"] as? String,',
"Info.plist parsing no longer reads CMUXCommit",
failures,
)
require(
content,
"if let commit = gitCommitHash(at: current) {",
"Project fallback no longer probes git commit hash",
failures,
)
require(
content,
'["git", "-C", directory.path, "rev-parse", "--short=9", "HEAD"]',
"Git commit probe command changed unexpectedly",
failures,
)
require(
content,
'normalizedCommitHash(ProcessInfo.processInfo.environment["CMUX_COMMIT"])',
"Environment commit fallback (CMUX_COMMIT) is missing",
failures,
)
if failures:
print("FAIL: CLI version commit metadata regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: CLI version commit metadata wiring is intact")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,118 +0,0 @@
#!/usr/bin/env python3
"""Regression test for command-palette socket-listener restart command wiring."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
content_view_path = repo_root / "Sources" / "ContentView.swift"
app_delegate_path = repo_root / "Sources" / "AppDelegate.swift"
missing_paths = [
str(path)
for path in [content_view_path, app_delegate_path]
if not path.exists()
]
if missing_paths:
print("Missing expected files:")
for path in missing_paths:
print(f" - {path}")
return 1
content_view = read_text(content_view_path)
app_delegate = read_text(app_delegate_path)
failures: list[str] = []
require(
content_view,
'commandId: "palette.restartSocketListener"',
"Missing `palette.restartSocketListener` command contribution",
failures,
)
require(
content_view,
'title: constant("Restart CLI Listener")',
"Missing `Restart CLI Listener` command title",
failures,
)
require(
content_view,
'registry.register(commandId: "palette.restartSocketListener") {',
"Missing command handler registration for `palette.restartSocketListener`",
failures,
)
require(
content_view,
"AppDelegate.shared?.restartSocketListener(nil)",
"Socket restart command handler does not call `AppDelegate.restartSocketListener`",
failures,
)
require(
app_delegate,
"@objc func restartSocketListener(_ sender: Any?) {",
"Missing `AppDelegate.restartSocketListener` action",
failures,
)
require(
app_delegate,
"private func socketListenerConfigurationIfEnabled() -> (mode: SocketControlMode, path: String)? {",
"Missing shared socket listener configuration helper",
failures,
)
require(
app_delegate,
'restartSocketListenerIfEnabled(source: "menu.command")',
"`restartSocketListener` no longer delegates to restart helper",
failures,
)
require(
app_delegate,
"TerminalController.shared.stop()",
"`restartSocketListenerIfEnabled` no longer stops current listener before restart",
failures,
)
require(
app_delegate,
"TerminalController.shared.start(tabManager: tabManager, socketPath: config.path, accessMode: config.mode)",
"`restartSocketListenerIfEnabled` no longer starts listener with current settings",
failures,
)
if failures:
print("FAIL: command-palette socket restart command regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: command-palette socket restart command wiring is intact")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,126 +0,0 @@
#!/usr/bin/env python3
"""Regression test for command-palette update command wiring."""
from __future__ import annotations
import re
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def expect_regex(content: str, pattern: str, message: str, failures: list[str]) -> None:
if re.search(pattern, content, flags=re.DOTALL) is None:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
content_view_path = repo_root / "Sources" / "ContentView.swift"
app_delegate_path = repo_root / "Sources" / "AppDelegate.swift"
controller_path = repo_root / "Sources" / "Update" / "UpdateController.swift"
missing_paths = [
str(path)
for path in [content_view_path, app_delegate_path, controller_path]
if not path.exists()
]
if missing_paths:
print("Missing expected files:")
for path in missing_paths:
print(f" - {path}")
return 1
content_view = read_text(content_view_path)
app_delegate = read_text(app_delegate_path)
controller = read_text(controller_path)
failures: list[str] = []
expect_regex(
content_view,
r'static\s+let\s+updateHasAvailable\s*=\s*"update\.hasAvailable"',
"Missing `CommandPaletteContextKeys.updateHasAvailable`",
failures,
)
expect_regex(
content_view,
r'if\s+case\s+\.updateAvailable\s*=\s*updateViewModel\.effectiveState\s*\{\s*snapshot\.setBool\(CommandPaletteContextKeys\.updateHasAvailable,\s*true\)\s*\}',
"Command palette context no longer tracks update-available state",
failures,
)
expect_regex(
content_view,
r'commandId:\s*"palette\.applyUpdateIfAvailable".*?title:\s*constant\("Apply Update \(If Available\)"\).*?keywords:\s*\[[^\]]*"apply"[^\]]*"install"[^\]]*"update"[^\]]*"available"[^\]]*\].*?when:\s*\{\s*\$0\.bool\(CommandPaletteContextKeys\.updateHasAvailable\)\s*\}',
"Missing or incomplete `palette.applyUpdateIfAvailable` contribution visibility gating",
failures,
)
expect_regex(
content_view,
r'commandId:\s*"palette\.attemptUpdate".*?title:\s*constant\("Attempt Update"\).*?keywords:\s*\[[^\]]*"attempt"[^\]]*"check"[^\]]*"update"[^\]]*\]',
"Missing or incomplete `palette.attemptUpdate` contribution",
failures,
)
expect_regex(
content_view,
r'registry\.register\(commandId:\s*"palette\.applyUpdateIfAvailable"\)\s*\{\s*AppDelegate\.shared\?\.applyUpdateIfAvailable\(nil\)\s*\}',
"Missing handler registration for `palette.applyUpdateIfAvailable`",
failures,
)
expect_regex(
content_view,
r'registry\.register\(commandId:\s*"palette\.attemptUpdate"\)\s*\{\s*AppDelegate\.shared\?\.attemptUpdate\(nil\)\s*\}',
"Missing handler registration for `palette.attemptUpdate`",
failures,
)
expect_regex(
app_delegate,
r'@objc\s+func\s+applyUpdateIfAvailable\(_\s+sender:\s+Any\?\)\s*\{\s*updateViewModel\.overrideState\s*=\s*nil\s*updateController\.installUpdate\(\)\s*\}',
"`AppDelegate.applyUpdateIfAvailable` is missing or does not call `updateController.installUpdate()`",
failures,
)
expect_regex(
app_delegate,
r'@objc\s+func\s+attemptUpdate\(_\s+sender:\s+Any\?\)\s*\{\s*updateViewModel\.overrideState\s*=\s*nil\s*updateController\.attemptUpdate\(\)\s*\}',
"`AppDelegate.attemptUpdate` is missing or does not call `updateController.attemptUpdate()`",
failures,
)
expect_regex(
controller,
r'func\s+attemptUpdate\(\)\s*\{',
"`UpdateController.attemptUpdate()` is missing",
failures,
)
if "state.confirm()" not in controller:
failures.append("`UpdateController.attemptUpdate()` no longer auto-confirms update installation")
if "checkForUpdates()" not in controller:
failures.append("`UpdateController.attemptUpdate()` no longer triggers a check before install")
if failures:
print("FAIL: command-palette update command regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: command-palette update commands expose apply + attempt wiring")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,159 +0,0 @@
#!/usr/bin/env python3
"""
Automated test for ctrl+enter keybind using real keystrokes.
Requires:
- cmux running
- Accessibility permissions for System Events (osascript)
- keybind = ctrl+enter=text:\\r (or \\n/\\x0d) configured in Ghostty config
"""
import os
import sys
import time
import subprocess
from pathlib import Path
from typing import Optional
# Add the directory containing cmux.py to the path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from cmux import cmux, cmuxError
def run_osascript(script: str) -> subprocess.CompletedProcess[str]:
# Use capture_output so we can detect common permission failures and skip.
result = subprocess.run(
["osascript", "-e", script],
capture_output=True,
text=True,
)
if result.returncode != 0:
raise subprocess.CalledProcessError(
result.returncode,
result.args,
output=result.stdout,
stderr=result.stderr,
)
return result
def is_keystroke_permission_error(err: subprocess.CalledProcessError) -> bool:
text = f"{getattr(err, 'stderr', '') or ''}\n{getattr(err, 'output', '') or ''}"
return "not allowed to send keystrokes" in text or "(1002)" in text
def has_ctrl_enter_keybind(config_text: str) -> bool:
for line in config_text.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
if "ctrl+enter" in stripped and "text:" in stripped:
if "\\r" in stripped or "\\n" in stripped or "\\x0d" in stripped:
return True
return False
def find_config_with_keybind() -> Optional[Path]:
home = Path.home()
candidates = [
home / "Library/Application Support/com.mitchellh.ghostty/config.ghostty",
home / "Library/Application Support/com.mitchellh.ghostty/config",
home / ".config/ghostty/config.ghostty",
home / ".config/ghostty/config",
]
for path in candidates:
if not path.exists():
continue
try:
if has_ctrl_enter_keybind(path.read_text(encoding="utf-8")):
return path
except OSError:
continue
return None
def test_ctrl_enter_keybind(client: cmux) -> tuple[bool, str]:
marker = Path("/tmp") / f"ghostty_ctrl_enter_{os.getpid()}"
marker.unlink(missing_ok=True)
# Create a fresh tab to avoid interfering with existing sessions
new_tab_id = client.new_tab()
client.select_tab(new_tab_id)
time.sleep(0.3)
try:
# Make sure the app is focused for keystrokes
bundle_id = cmux.default_bundle_id()
run_osascript(f'tell application id "{bundle_id}" to activate')
time.sleep(0.2)
# Clear any running command
try:
client.send_key("ctrl-c")
time.sleep(0.2)
except Exception:
pass
# Type the command (without pressing Enter)
run_osascript(f'tell application "System Events" to keystroke "touch {marker}"')
time.sleep(0.1)
# Send Ctrl+Enter (key code 36 = Return)
run_osascript('tell application "System Events" to key code 36 using control down')
time.sleep(0.5)
ok = marker.exists()
return ok, ("Ctrl+Enter keybind executed command" if ok else "Marker not created by Ctrl+Enter")
finally:
if marker.exists():
marker.unlink(missing_ok=True)
try:
client.close_tab(new_tab_id)
except Exception:
pass
def run_tests() -> int:
print("=" * 60)
print("cmux Ctrl+Enter Keybind Test")
print("=" * 60)
print()
socket_path = cmux.default_socket_path()
if not os.path.exists(socket_path):
print(f"SKIP: Socket not found at {socket_path}")
print("Tip: start cmux first (or set CMUX_TAG / CMUX_SOCKET_PATH).")
return 0
config_path = find_config_with_keybind()
if not config_path:
print("SKIP: Required keybind not found in Ghostty config.")
print("Expected a line like: keybind = ctrl+enter=text:\\r")
return 0
print(f"Using keybind from: {config_path}")
print()
try:
with cmux() as client:
ok, message = test_ctrl_enter_keybind(client)
status = "" if ok else ""
print(f"{status} {message}")
return 0 if ok else 1
except cmuxError as e:
print(f"SKIP: {e}")
return 0
except subprocess.CalledProcessError as e:
if is_keystroke_permission_error(e):
print("SKIP: osascript/System Events not allowed to send keystrokes (Accessibility permission missing)")
return 0
print(f"Error: osascript failed: {e}")
if getattr(e, "stderr", None):
print(e.stderr.strip())
if getattr(e, "output", None):
print(e.output.strip())
return 1
if __name__ == "__main__":
sys.exit(run_tests())

View file

@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""Static regression checks for re-entrant terminal focus guard.
Guards the fix for split-drag focus churn where:
becomeFirstResponder -> onFocus -> Workspace.focusPanel -> refocus side-effects
could repeatedly re-enter and spike CPU.
"""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def main() -> int:
root = repo_root()
failures: list[str] = []
workspace_path = root / "Sources" / "Workspace.swift"
workspace_source = workspace_path.read_text(encoding="utf-8")
required_workspace_snippets = [
"enum FocusPanelTrigger {",
"case terminalFirstResponder",
"trigger: FocusPanelTrigger = .standard",
"let shouldSuppressReentrantRefocus = trigger == .terminalFirstResponder && selectionAlreadyConverged",
"if let targetPaneId, !shouldSuppressReentrantRefocus {",
"reason=firstResponderAlreadyConverged",
]
for snippet in required_workspace_snippets:
if snippet not in workspace_source:
failures.append(f"Workspace focus guard missing snippet: {snippet}")
workspace_content_view_path = root / "Sources" / "WorkspaceContentView.swift"
workspace_content_view_source = workspace_content_view_path.read_text(encoding="utf-8")
focus_callback_snippet = "workspace.focusPanel(panel.id, trigger: .terminalFirstResponder)"
if focus_callback_snippet not in workspace_content_view_source:
failures.append(
"WorkspaceContentView terminal onFocus callback no longer passes .terminalFirstResponder trigger"
)
if failures:
print("FAIL: focus-panel re-entrant guard regression checks failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: focus-panel re-entrant guard is in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,166 +0,0 @@
#!/usr/bin/env python3
"""Regression guard for issue #494 (post-wake sidebar git updates freezing)."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
zsh_path = repo_root / "Resources" / "shell-integration" / "cmux-zsh-integration.zsh"
bash_path = repo_root / "Resources" / "shell-integration" / "cmux-bash-integration.bash"
app_delegate_path = repo_root / "Sources" / "AppDelegate.swift"
required_paths = [zsh_path, bash_path, app_delegate_path]
missing_paths = [str(path) for path in required_paths if not path.exists()]
if missing_paths:
print("Missing expected files:")
for path in missing_paths:
print(f" - {path}")
return 1
zsh_content = read_text(zsh_path)
bash_content = read_text(bash_path)
app_delegate = read_text(app_delegate_path)
failures: list[str] = []
require(
zsh_content,
"_CMUX_GIT_JOB_STARTED_AT",
"zsh integration is missing git probe start tracking",
failures,
)
require(
zsh_content,
"_CMUX_PR_JOB_STARTED_AT",
"zsh integration is missing PR probe start tracking",
failures,
)
require(
zsh_content,
"_CMUX_ASYNC_JOB_TIMEOUT",
"zsh integration is missing async probe timeout guard",
failures,
)
require(
zsh_content,
"now - _CMUX_GIT_JOB_STARTED_AT >= _CMUX_ASYNC_JOB_TIMEOUT",
"zsh integration no longer clears stale git probe PID after timeout",
failures,
)
require(
zsh_content,
"now - _CMUX_PR_JOB_STARTED_AT >= _CMUX_ASYNC_JOB_TIMEOUT",
"zsh integration no longer clears stale PR probe PID after timeout",
failures,
)
require(
zsh_content,
"ncat -w 1 -U \"$CMUX_SOCKET_PATH\" --send-only",
"zsh integration missing ncat socket timeout",
failures,
)
require(
zsh_content,
"socat -T 1 - \"UNIX-CONNECT:$CMUX_SOCKET_PATH\"",
"zsh integration missing socat socket timeout",
failures,
)
require(
bash_content,
"_CMUX_GIT_JOB_STARTED_AT",
"bash integration is missing git probe start tracking",
failures,
)
require(
bash_content,
"_CMUX_PR_JOB_STARTED_AT",
"bash integration is missing PR probe start tracking",
failures,
)
require(
bash_content,
"_CMUX_ASYNC_JOB_TIMEOUT",
"bash integration is missing async probe timeout guard",
failures,
)
require(
bash_content,
"now - _CMUX_GIT_JOB_STARTED_AT >= _CMUX_ASYNC_JOB_TIMEOUT",
"bash integration no longer clears stale git probe PID after timeout",
failures,
)
require(
bash_content,
"now - _CMUX_PR_JOB_STARTED_AT >= _CMUX_ASYNC_JOB_TIMEOUT",
"bash integration no longer clears stale PR probe PID after timeout",
failures,
)
require(
bash_content,
"ncat -w 1 -U \"$CMUX_SOCKET_PATH\" --send-only",
"bash integration missing ncat socket timeout",
failures,
)
require(
bash_content,
"socat -T 1 - \"UNIX-CONNECT:$CMUX_SOCKET_PATH\"",
"bash integration missing socat socket timeout",
failures,
)
require(
app_delegate,
"NSWorkspace.didWakeNotification",
"AppDelegate is missing wake observer for socket listener recovery",
failures,
)
require(
app_delegate,
"restartSocketListenerIfEnabled(source: \"workspace.didWake\")",
"Wake observer no longer re-arms the socket listener",
failures,
)
require(
app_delegate,
"private func restartSocketListenerIfEnabled(source: String)",
"Missing shared socket-listener restart helper",
failures,
)
if failures:
print("FAIL: issue #494 regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: issue #494 sleep/wake recovery guards are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,126 +0,0 @@
#!/usr/bin/env python3
"""Regression guard for issue #582 (sidebar git branch updates stalling)."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def extract_function(content: str, signature: str) -> str:
start = content.find(signature)
if start < 0:
return ""
brace = content.find("{", start)
if brace < 0:
return ""
depth = 0
for idx in range(brace, len(content)):
ch = content[idx]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return content[start : idx + 1]
return ""
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
terminal_controller_path = repo_root / "Sources" / "TerminalController.swift"
if not terminal_controller_path.exists():
print(f"Missing expected file: {terminal_controller_path}")
return 1
terminal_controller = terminal_controller_path.read_text(encoding="utf-8")
report_body = extract_function(terminal_controller, "private func reportGitBranch(_ args: String) -> String")
clear_body = extract_function(terminal_controller, "private func clearGitBranch(_ args: String) -> String")
failures: list[str] = []
if not report_body:
failures.append("Unable to locate reportGitBranch implementation")
if not clear_body:
failures.append("Unable to locate clearGitBranch implementation")
if report_body:
require(
report_body,
"if let scope = Self.explicitSocketScope(options: parsed.options)",
"reportGitBranch is missing explicit-scope fast path",
failures,
)
require(
report_body,
"DispatchQueue.main.async",
"reportGitBranch no longer schedules explicit-scope updates with main.async",
failures,
)
require(
report_body,
"tab.updatePanelGitBranch(panelId: scope.panelId",
"reportGitBranch fast path no longer writes branch state to the scoped panel",
failures,
)
require(
report_body,
"DispatchQueue.main.sync",
"reportGitBranch lost sync fallback path for non-explicit/manual calls",
failures,
)
if clear_body:
require(
clear_body,
"if let scope = Self.explicitSocketScope(options: parsed.options)",
"clearGitBranch is missing explicit-scope fast path",
failures,
)
require(
clear_body,
"DispatchQueue.main.async",
"clearGitBranch no longer schedules explicit-scope clears with main.async",
failures,
)
require(
clear_body,
"tab.clearPanelGitBranch(panelId: scope.panelId)",
"clearGitBranch fast path no longer clears branch state for the scoped panel",
failures,
)
require(
clear_body,
"DispatchQueue.main.sync",
"clearGitBranch lost sync fallback path for non-explicit/manual calls",
failures,
)
if failures:
print("FAIL: issue #582 regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: issue #582 git branch socket fast path guards are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,105 +0,0 @@
#!/usr/bin/env python3
"""Regression guard for issue #666 (sidebar branch stuck after checkout)."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
zsh_path = repo_root / "Resources" / "shell-integration" / "cmux-zsh-integration.zsh"
bash_path = repo_root / "Resources" / "shell-integration" / "cmux-bash-integration.bash"
required_paths = [zsh_path, bash_path]
missing_paths = [str(path) for path in required_paths if not path.exists()]
if missing_paths:
print("Missing expected files:")
for path in missing_paths:
print(f" - {path}")
return 1
zsh_content = zsh_path.read_text(encoding="utf-8")
bash_content = bash_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
zsh_content,
"_CMUX_GIT_HEAD_SIGNATURE",
"zsh integration is missing git HEAD signature tracking",
failures,
)
require(
zsh_content,
"_cmux_git_head_signature",
"zsh integration is missing git HEAD signature helper",
failures,
)
require(
zsh_content,
'"$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE"',
"zsh integration no longer compares git HEAD signatures",
failures,
)
require(
zsh_content,
"_CMUX_GIT_FORCE=1",
"zsh integration no longer forces git probe refresh on HEAD changes",
failures,
)
require(
bash_content,
"_CMUX_GIT_HEAD_SIGNATURE",
"bash integration is missing git HEAD signature tracking",
failures,
)
require(
bash_content,
"_cmux_git_head_signature",
"bash integration is missing git HEAD signature helper",
failures,
)
require(
bash_content,
"git_head_changed=1",
"bash integration no longer flags HEAD changes for immediate refresh",
failures,
)
require(
bash_content,
'|| "$git_head_changed" == "1"',
"bash integration no longer restarts running git probes on HEAD change",
failures,
)
if failures:
print("FAIL: issue #666 regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: issue #666 checkout refresh guards are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,162 +0,0 @@
#!/usr/bin/env python3
"""Regression guard for issue #952 (flaky CLI socket connections)."""
from __future__ import annotations
import re
import shutil
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
"""Return the repository root for source inspections."""
fallback_root = Path(__file__).resolve().parents[1]
git_path = shutil.which("git")
if git_path is None:
return fallback_root
try:
result = subprocess.run(
[git_path, "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
except OSError:
return fallback_root
if result.returncode == 0:
return Path(result.stdout.strip())
return fallback_root
def require(content: str, needle: str, message: str, failures: list[str], *, regex: bool = False) -> None:
"""Record a failure when a required source pattern is missing."""
matched = re.search(needle, content, re.MULTILINE) is not None if regex else needle in content
if not matched:
failures.append(message)
def collect_failures() -> list[str]:
"""Collect missing source-level guards for the socket listener recovery fix."""
repo_root = get_repo_root()
terminal_controller_path = repo_root / "Sources" / "TerminalController.swift"
app_delegate_path = repo_root / "Sources" / "AppDelegate.swift"
failures: list[str] = []
missing_paths = [
str(path) for path in [terminal_controller_path, app_delegate_path] if not path.exists()
]
if missing_paths:
for path in missing_paths:
failures.append(f"Missing expected file: {path}")
return failures
terminal_controller = terminal_controller_path.read_text(encoding="utf-8")
app_delegate = app_delegate_path.read_text(encoding="utf-8")
require(
terminal_controller,
"let socketProbePerformed: Bool",
"Socket health snapshot no longer tracks whether connectability was probed",
failures,
)
require(
terminal_controller,
"let socketConnectable: Bool?",
"Socket health snapshot no longer distinguishes unprobed vs connectable sockets",
failures,
)
require(
terminal_controller,
"let socketConnectErrno: Int32?",
"Socket health snapshot no longer preserves probe errno",
failures,
)
require(
terminal_controller,
"signals.append(\"socket_unreachable\")",
"Socket health failures no longer flag unreachable listeners",
failures,
)
require(
terminal_controller,
r"private\s+nonisolated\s+static\s+func\s+probeSocketConnectability\s*\(\s*path:\s*String\s*\)",
"Missing active socket connectability probe helper",
failures,
regex=True,
)
require(
terminal_controller,
r"connect\s*\(\s*probeSocket\s*,\s*sockaddrPtr\s*,\s*socklen_t\s*\(\s*MemoryLayout<sockaddr_un>\.size\s*\)\s*\)",
"Socket health probe no longer performs a real connect() check",
failures,
regex=True,
)
require(
terminal_controller,
"stage: \"bind_path_too_long\"",
"Socket listener start no longer reports overlong Unix socket paths",
failures,
)
require(
terminal_controller,
"Self.unixSocketPathMaxLength",
"Socket listener path-length telemetry was removed",
failures,
)
require(
app_delegate,
"private static let socketListenerHealthCheckInterval: DispatchTimeInterval = .seconds(2)",
"Socket health timer interval drifted from the fast recovery setting",
failures,
)
require(
app_delegate,
"\"socketProbePerformed\": health.socketProbePerformed ? 1 : 0",
"Health telemetry no longer records whether a connectability probe ran",
failures,
)
require(
app_delegate,
"if let socketConnectable = health.socketConnectable {",
"Health telemetry no longer gates connectability on an actual probe result",
failures,
)
require(
app_delegate,
"data[\"socketConnectable\"] = socketConnectable ? 1 : 0",
"Health telemetry no longer includes connectability when a probe ran",
failures,
)
require(
app_delegate,
"if let socketConnectErrno = health.socketConnectErrno {",
"Health telemetry no longer records connect probe errno when available",
failures,
)
return failures
def test_issue_952_socket_listener_recovery() -> None:
"""Keep the source-level recovery guards for issue #952 in CI."""
failures = collect_failures()
assert not failures, "issue #952 regression(s) detected:\n- " + "\n- ".join(failures)
def main() -> int:
"""Run the regression guard without requiring pytest to be installed."""
failures = collect_failures()
if failures:
print("FAIL: issue #952 regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: issue #952 socket listener recovery guards are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,190 +0,0 @@
#!/usr/bin/env python3
"""
Lint test to catch SwiftUI patterns that cause performance issues.
This test checks for:
1. Text(_:style:) with auto-updating date styles (.time, .timer, .relative)
These cause continuous view updates and can lead to high CPU usage.
"""
from __future__ import annotations
import re
import subprocess
import sys
from pathlib import Path
from typing import List, Tuple
def get_repo_root():
"""Get the repository root directory."""
# Try git first
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
# Fall back to finding GhosttyTabs directory
cwd = Path.cwd()
if cwd.name == "GhosttyTabs" or (cwd / "Sources").exists():
return cwd
if (cwd.parent / "GhosttyTabs").exists():
return cwd.parent / "GhosttyTabs"
# Last resort: use current directory
return cwd
def find_swift_files(repo_root: Path) -> List[Path]:
"""Find all Swift files in Sources directory (excluding vendored code)."""
sources_dir = repo_root / "Sources"
if not sources_dir.exists():
return []
return list(sources_dir.rglob("*.swift"))
def check_autoupdating_text_styles(files: List[Path]) -> List[Tuple[Path, int, str]]:
"""
Check for Text(_:style:) with auto-updating date styles.
These patterns cause continuous SwiftUI view updates:
- Text(date, style: .time) - updates every second/minute
- Text(date, style: .timer) - updates continuously
- Text(date, style: .relative) - updates periodically
- Text(date, style: .offset) - updates periodically
Instead, use static formatting:
- Text(date.formatted(date: .omitted, time: .shortened))
"""
violations = []
# Patterns that indicate auto-updating Text with Date
# The key patterns are: Text(something, style: .time/timer/relative/offset)
problematic_patterns = [
"style: .time",
"style: .timer",
"style: .relative",
"style: .offset",
"style:.time",
"style:.timer",
"style:.relative",
"style:.offset",
]
for file_path in files:
try:
content = file_path.read_text()
lines = content.split('\n')
for line_num, line in enumerate(lines, start=1):
# Skip comments
stripped = line.strip()
if stripped.startswith("//"):
continue
for pattern in problematic_patterns:
if pattern in line:
violations.append((file_path, line_num, line.strip()))
break
except Exception as e:
print(f"Warning: Could not read {file_path}: {e}", file=sys.stderr)
return violations
def check_command_palette_caret_tint(repo_root: Path) -> List[str]:
"""Ensure command palette text inputs keep a white caret tint."""
content_view = repo_root / "Sources" / "ContentView.swift"
if not content_view.exists():
return [f"Missing expected file: {content_view}"]
try:
content = content_view.read_text()
except Exception as e:
return [f"Could not read {content_view}: {e}"]
checks = [
(
"search input",
r"TextField\(commandPaletteSearchPlaceholder, text: \$commandPaletteQuery\)(?P<body>.*?)"
r"\.focused\(\$isCommandPaletteSearchFocused\)",
),
(
"rename input",
r"TextField\(target\.placeholder, text: \$commandPaletteRenameDraft\)(?P<body>.*?)"
r"\.focused\(\$isCommandPaletteRenameFocused\)",
),
]
violations: List[str] = []
for label, pattern in checks:
match = re.search(pattern, content, flags=re.DOTALL)
if not match:
violations.append(
f"Could not locate command palette {label} TextField block in Sources/ContentView.swift"
)
continue
body = match.group("body")
if ".tint(.white)" not in body:
violations.append(
f"Command palette {label} TextField must use `.tint(.white)` in Sources/ContentView.swift"
)
return violations
def main():
"""Run the lint checks."""
repo_root = get_repo_root()
swift_files = find_swift_files(repo_root)
print(f"Checking {len(swift_files)} Swift files for performance issues...")
# Check for auto-updating Text styles
style_violations = check_autoupdating_text_styles(swift_files)
tint_violations = check_command_palette_caret_tint(repo_root)
has_failures = False
if style_violations:
has_failures = True
print("\n❌ LINT FAILURES: Auto-updating Text styles found")
print("=" * 60)
print("These patterns cause continuous SwiftUI view updates and high CPU usage:")
print()
for file_path, line_num, line in style_violations:
rel_path = file_path.relative_to(repo_root)
print(f" {rel_path}:{line_num}")
print(f" {line}")
print()
print("FIX: Replace with static formatting:")
print(" Instead of: Text(date, style: .time)")
print(" Use: Text(date.formatted(date: .omitted, time: .shortened))")
print()
if tint_violations:
has_failures = True
print("\n❌ LINT FAILURES: Command palette caret tint drifted")
print("=" * 60)
print("The command palette search and rename text fields must keep a white caret:")
print()
for message in tint_violations:
print(f" {message}")
print()
print("FIX: Set command palette TextField tint modifiers to `.white`.")
print()
if has_failures:
return 1
print("✅ No linted SwiftUI pattern regressions found")
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -1,126 +0,0 @@
#!/usr/bin/env python3
"""Regression tests for markdown-open CLI parsing/help behavior."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
cli_path = repo_root / "CLI" / "cmux.swift"
panel_path = repo_root / "Sources" / "Panels" / "MarkdownPanel.swift"
if not cli_path.exists():
print(f"FAIL: missing expected file: {cli_path}")
return 1
if not panel_path.exists():
print(f"FAIL: missing expected file: {panel_path}")
return 1
cli_content = cli_path.read_text(encoding="utf-8")
panel_content = panel_path.read_text(encoding="utf-8")
failures: list[str] = []
# CLI parser behavior.
require(
cli_content,
'if let first = args.first, first.lowercased() == "open" {',
"markdown parser should explicitly support the 'open' subcommand",
failures,
)
require(
cli_content,
"args.count == 1",
"markdown parser should accept single-arg shorthand path",
failures,
)
require(
cli_content,
"args.count == 1, let first = args.first, !first.hasPrefix(\"-\")",
"markdown parser should reject option-like single args from shorthand path mode",
failures,
)
require(
cli_content,
"let trailingArgs = Array(subArgs.dropFirst())",
"markdown parser should validate trailing arguments",
failures,
)
require(
cli_content,
'trailingArgs.first(where: { $0.hasPrefix("-") })',
"markdown parser should detect unknown trailing flags",
failures,
)
require(
cli_content,
"markdown open: unexpected argument",
"markdown parser should error on unexpected trailing args",
failures,
)
# Help text should document shorthand and full index handle support.
require(
cli_content,
"Usage: cmux markdown open <path> [options]\n cmux markdown <path> (shorthand for 'open')",
"markdown subcommand help should include shorthand usage",
failures,
)
require(
cli_content,
"--window <id|ref|index> Target window",
"markdown subcommand help should document window index handles",
failures,
)
require(
cli_content,
"markdown [open] <path> (open markdown file in formatted viewer panel with live reload)",
"top-level help should include markdown shorthand syntax",
failures,
)
# Session restore edge case: file missing at startup should still attempt reconnect.
require(
panel_content,
"if isFileUnavailable && fileWatchSource == nil {",
"MarkdownPanel should schedule reattach when watcher cannot start at init",
failures,
)
require(
panel_content,
"scheduleReattach(attempt: 1)",
"MarkdownPanel should trigger reattach retries for missing files",
failures,
)
if failures:
print("FAIL: markdown-open regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: markdown-open CLI/help/reattach regression checks are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,79 +0,0 @@
#!/usr/bin/env python3
"""Regression test: cmux advertises media-capture access metadata."""
from __future__ import annotations
import plistlib
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def load_plist(path: Path, failures: list[str]) -> dict:
if not path.exists():
failures.append(f"Missing expected file: {path}")
return {}
with path.open("rb") as f:
return plistlib.load(f)
def main() -> int:
repo_root = get_repo_root()
failures: list[str] = []
info = load_plist(repo_root / "Resources" / "Info.plist", failures)
entitlements = load_plist(repo_root / "cmux.entitlements", failures)
mic_usage = info.get("NSMicrophoneUsageDescription")
camera_usage = info.get("NSCameraUsageDescription")
if not isinstance(mic_usage, str) or not mic_usage.strip():
failures.append(
"Resources/Info.plist must define a non-empty NSMicrophoneUsageDescription"
)
elif mic_usage.strip() != "A program running within cmux would like to use your microphone.":
failures.append(
"Resources/Info.plist NSMicrophoneUsageDescription should match the Ghostty-style wording"
)
if entitlements.get("com.apple.security.device.audio-input") is not True:
failures.append(
"cmux.entitlements must set com.apple.security.device.audio-input to true"
)
if not isinstance(camera_usage, str) or not camera_usage.strip():
failures.append(
"Resources/Info.plist must define a non-empty NSCameraUsageDescription"
)
elif camera_usage.strip() != "A program running within cmux would like to use your camera.":
failures.append(
"Resources/Info.plist NSCameraUsageDescription should match the Ghostty-style wording"
)
if entitlements.get("com.apple.security.device.camera") is not True:
failures.append(
"cmux.entitlements must set com.apple.security.device.camera to true"
)
if failures:
print("FAIL: media-capture metadata regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: microphone/camera usage descriptions and entitlements are present")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,46 +0,0 @@
#!/usr/bin/env python3
"""
Regression test for the default sidebar active workspace indicator style.
"""
from __future__ import annotations
import re
import subprocess
import sys
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def main() -> int:
repo_root = get_repo_root()
tab_manager = repo_root / "Sources" / "TabManager.swift"
if not tab_manager.exists():
print(f"FAIL: Missing file {tab_manager}")
return 1
content = tab_manager.read_text(encoding="utf-8")
pattern = r"static let defaultStyle:\s*SidebarActiveTabIndicatorStyle\s*=\s*\.leftRail\b"
if re.search(pattern, content) is None:
rel = tab_manager.relative_to(repo_root)
print(f"FAIL: Expected default style `.leftRail` in {rel}")
return 1
print("PASS: sidebar indicator default style is left rail")
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -1,113 +0,0 @@
#!/usr/bin/env python3
"""Static regression checks for terminal tiny-pane resize/overflow fixes.
Guards the key invariants for issue #348:
1) Terminal portal sync must stabilize layout and clamp hosted frames to host bounds.
2) Surface sizing must prefer live bounds over stale pending values when available.
"""
from __future__ import annotations
import subprocess
from pathlib import Path
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path(__file__).resolve().parents[1]
def extract_block(source: str, signature: str) -> str:
start = source.find(signature)
if start < 0:
raise ValueError(f"Missing signature: {signature}")
brace_start = source.find("{", start)
if brace_start < 0:
raise ValueError(f"Missing opening brace for: {signature}")
depth = 0
for idx in range(brace_start, len(source)):
char = source[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return source[brace_start : idx + 1]
raise ValueError(f"Unbalanced braces for: {signature}")
def main() -> int:
root = repo_root()
failures: list[str] = []
portal_path = root / "Sources" / "TerminalWindowPortal.swift"
portal_source = portal_path.read_text(encoding="utf-8")
if "hostView.layer?.masksToBounds = true" not in portal_source:
failures.append("WindowTerminalPortal init no longer enables hostView layer clipping")
if "hostView.postsFrameChangedNotifications = true" not in portal_source:
failures.append("WindowTerminalPortal init no longer enables hostView frame-change notifications")
if "hostView.postsBoundsChangedNotifications = true" not in portal_source:
failures.append("WindowTerminalPortal init no longer enables hostView bounds-change notifications")
if "private func synchronizeLayoutHierarchy()" not in portal_source:
failures.append("WindowTerminalPortal missing synchronizeLayoutHierarchy()")
if "private func synchronizeHostFrameToReference() -> Bool" not in portal_source:
failures.append("WindowTerminalPortal missing synchronizeHostFrameToReference()")
if "hostedView.reconcileGeometryNow()" not in extract_block(
portal_source,
"func bind(hostedView: GhosttySurfaceScrollView, to anchorView: NSView, visibleInUI: Bool, zPriority: Int = 0)",
):
failures.append("bind() no longer pre-reconciles hosted geometry before attach")
sync_block = extract_block(portal_source, "private func synchronizeHostedView(withId hostedId: ObjectIdentifier)")
for required in [
"let hostBounds = hostView.bounds",
"let clampedFrame = frameInHost.intersection(hostBounds)",
"let targetFrame = (hasFiniteFrame && hasVisibleIntersection) ? clampedFrame : frameInHost",
"hostedView.reconcileGeometryNow()",
"hostedView.refreshSurfaceNow()",
]:
if required not in sync_block:
failures.append(f"terminal portal sync missing: {required}")
if (
"scheduleDeferredFullSynchronizeAll()" not in sync_block
and "scheduleTransientRecoveryRetryIfNeeded(" not in sync_block
):
failures.append(
"terminal portal sync no longer schedules deferred recovery for transient geometry states"
)
terminal_view_path = root / "Sources" / "GhosttyTerminalView.swift"
terminal_view_source = terminal_view_path.read_text(encoding="utf-8")
resolved_block = extract_block(terminal_view_source, "private func resolvedSurfaceSize(preferred size: CGSize?) -> CGSize")
bounds_index = resolved_block.find("let currentBounds = bounds.size")
pending_index = resolved_block.find("if let pending = pendingSurfaceSize")
if bounds_index < 0 or pending_index < 0 or bounds_index > pending_index:
failures.append("resolvedSurfaceSize() no longer prefers bounds before pendingSurfaceSize")
update_block = extract_block(terminal_view_source, "private func updateSurfaceSize(size: CGSize? = nil)")
if "let size = resolvedSurfaceSize(preferred: size)" not in update_block:
failures.append("updateSurfaceSize() no longer resolves size via resolvedSurfaceSize()")
if failures:
print("FAIL: terminal resize/portal regression guards failed")
for item in failures:
print(f" - {item}")
return 1
print("PASS: terminal resize/portal regression guards are in place")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -1,54 +0,0 @@
#!/usr/bin/env python3
"""
Verify update UI timing constants so update indicators are visible long enough.
"""
from pathlib import Path
import re
import sys
ROOT = Path(__file__).resolve().parents[1]
TIMING_FILE = ROOT / "Sources" / "Update" / "UpdateTiming.swift"
def read_constants(text: str) -> dict[str, float]:
constants = {}
pattern = re.compile(r"static let (\w+): TimeInterval = ([0-9.]+)")
for match in pattern.finditer(text):
constants[match.group(1)] = float(match.group(2))
return constants
def main() -> int:
if not TIMING_FILE.exists():
print(f"Missing {TIMING_FILE}")
return 1
constants = read_constants(TIMING_FILE.read_text())
required = {
"minimumCheckDisplayDuration": 2.0,
"noUpdateDisplayDuration": 5.0,
}
failures = []
for name, expected in required.items():
actual = constants.get(name)
if actual is None:
failures.append(f"{name} missing")
continue
if actual != expected:
failures.append(f"{name} = {actual} (expected {expected})")
if failures:
print("Update timing test failed:")
for failure in failures:
print(f" - {failure}")
return 1
print("Update timing test passed.")
return 0
if __name__ == "__main__":
sys.exit(main())