Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 8 additions & 43 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -465,54 +465,19 @@ jobs:
run: |
set -euo pipefail
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
HARNESS_DIR="${RUNNER_TEMP}/cmux-display-churn-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
mkdir -p "$HARNESS_DIR"
PREFIX="${HARNESS_DIR}/cmux-display-churn"
READY_PATH="${PREFIX}.ready"
DISPLAY_ID_PATH="${PREFIX}.id"
START_PATH="${PREFIX}.start"
DONE_PATH="${PREFIX}.done"
LOG_PATH="${PREFIX}.log"
MANIFEST_PATH="${HARNESS_DIR}/cmux-ui-test-display-harness.json"

rm -f "$READY_PATH" "$DISPLAY_ID_PATH" "$START_PATH" "$DONE_PATH" "$LOG_PATH" "$MANIFEST_PATH"
HELPER_PATH="/tmp/create-virtual-display"
MANIFEST_PATH="/tmp/cmux-ui-test-display-harness.json"

clang -framework Foundation -framework CoreGraphics \
-o /tmp/create-virtual-display scripts/create-virtual-display.m
rm -f "$MANIFEST_PATH"
trap 'rm -f "$MANIFEST_PATH"' EXIT

/tmp/create-virtual-display \
--modes 1920x1080,1728x1117,1600x900,1440x810 \
--ready-path "$READY_PATH" \
--display-id-path "$DISPLAY_ID_PATH" \
--start-path "$START_PATH" \
--done-path "$DONE_PATH" \
--iterations 40 \
--interval-ms 40 \
>"$LOG_PATH" 2>&1 &
VDISPLAY_PID=$!
trap 'kill "$VDISPLAY_PID" >/dev/null 2>&1 || true; rm -f "$MANIFEST_PATH"' EXIT

for _ in {1..120}; do
[ -f "$READY_PATH" ] && break
sleep 0.25
done
[ -f "$READY_PATH" ] || {
echo "Display harness failed to start" >&2
cat "$LOG_PATH" >&2 || true
exit 1
}
clang -framework Foundation -framework CoreGraphics \
-o "$HELPER_PATH" scripts/create-virtual-display.m

cat >"$MANIFEST_PATH" <<EOF
{"readyPath":"$READY_PATH","displayIDPath":"$DISPLAY_ID_PATH","startPath":"$START_PATH","donePath":"$DONE_PATH","logPath":"$LOG_PATH"}
{"helperBinaryPath":"$HELPER_PATH"}
EOF
echo "Display harness manifest: $MANIFEST_PATH"
cat "$MANIFEST_PATH"

CMUX_UI_TEST_DISPLAY_READY_PATH="$READY_PATH" \
CMUX_UI_TEST_DISPLAY_ID_PATH="$DISPLAY_ID_PATH" \
CMUX_UI_TEST_DISPLAY_START_PATH="$START_PATH" \
CMUX_UI_TEST_DISPLAY_DONE_PATH="$DONE_PATH" \
CMUX_UI_TEST_DISPLAY_LOG_PATH="$LOG_PATH" \

xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
-disableAutomaticPackageResolution \
Expand Down
54 changes: 8 additions & 46 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,56 +208,18 @@ jobs:
DISPLAY_ENV_PREFIX=()

if [ "$TEST_FILTER" = "DisplayResolutionRegressionUITests" ]; then
HARNESS_DIR="${RUNNER_TEMP}/cmux-display-churn-${{ github.run_id }}-${{ github.run_attempt }}"
mkdir -p "$HARNESS_DIR"
PREFIX="${HARNESS_DIR}/cmux-display-churn"
READY_PATH="${PREFIX}.ready"
DISPLAY_ID_PATH="${PREFIX}.id"
START_PATH="${PREFIX}.start"
DONE_PATH="${PREFIX}.done"
LOG_PATH="${PREFIX}.log"
MANIFEST_PATH="${HARNESS_DIR}/cmux-ui-test-display-harness.json"

rm -f "$READY_PATH" "$DISPLAY_ID_PATH" "$START_PATH" "$DONE_PATH" "$LOG_PATH" "$MANIFEST_PATH"
HELPER_PATH="/tmp/create-virtual-display"
MANIFEST_PATH="/tmp/cmux-ui-test-display-harness.json"

rm -f "$MANIFEST_PATH"
trap 'rm -f "$MANIFEST_PATH"' EXIT

clang -framework Foundation -framework CoreGraphics \
-o /tmp/create-virtual-display scripts/create-virtual-display.m

/tmp/create-virtual-display \
--modes 1920x1080,1728x1117,1600x900,1440x810 \
--ready-path "$READY_PATH" \
--display-id-path "$DISPLAY_ID_PATH" \
--start-path "$START_PATH" \
--done-path "$DONE_PATH" \
--iterations 40 \
--interval-ms 40 \
>"$LOG_PATH" 2>&1 &
DISPLAY_VDISPLAY_PID=$!
trap 'kill "${DISPLAY_VDISPLAY_PID:-}" >/dev/null 2>&1 || true; rm -f "$MANIFEST_PATH"' EXIT

for _ in {1..120}; do
[ -f "$READY_PATH" ] && break
sleep 0.25
done
[ -f "$READY_PATH" ] || {
echo "Display harness failed to start" >&2
cat "$LOG_PATH" >&2 || true
exit 1
}
-o "$HELPER_PATH" scripts/create-virtual-display.m

cat >"$MANIFEST_PATH" <<EOF
{"readyPath":"$READY_PATH","displayIDPath":"$DISPLAY_ID_PATH","startPath":"$START_PATH","donePath":"$DONE_PATH","logPath":"$LOG_PATH"}
EOF
echo "Display harness manifest: $MANIFEST_PATH"
cat "$MANIFEST_PATH"

DISPLAY_ENV_PREFIX=(
CMUX_UI_TEST_DISPLAY_READY_PATH="$READY_PATH"
CMUX_UI_TEST_DISPLAY_ID_PATH="$DISPLAY_ID_PATH"
CMUX_UI_TEST_DISPLAY_START_PATH="$START_PATH"
CMUX_UI_TEST_DISPLAY_DONE_PATH="$DONE_PATH"
CMUX_UI_TEST_DISPLAY_LOG_PATH="$LOG_PATH"
)
{"helperBinaryPath":"$HELPER_PATH"}
EOF
Comment on lines 220 to +222

@cubic-dev-ai cubic-dev-ai Bot Mar 18, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This heredoc terminator is indented, so Bash does not close the heredoc and the workflow step fails with a syntax error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/test-e2e.yml, line 220:

<comment>This heredoc terminator is indented, so Bash does not close the heredoc and the workflow step fails with a syntax error.</comment>

<file context>
@@ -209,13 +209,17 @@ jobs:
-            DISPLAY_ENV_PREFIX=(
-              CMUX_UI_TEST_DISPLAY_HELPER_BINARY_PATH="$HELPER_PATH"
-            )
+            cat >"$MANIFEST_PATH" <<EOF
+            {"helperBinaryPath":"$HELPER_PATH"}
+            EOF
</file context>
Suggested change
cat >"$MANIFEST_PATH" <<EOF
{"readyPath":"$READY_PATH","displayIDPath":"$DISPLAY_ID_PATH","startPath":"$START_PATH","donePath":"$DONE_PATH","logPath":"$LOG_PATH"}
EOF
echo "Display harness manifest: $MANIFEST_PATH"
cat "$MANIFEST_PATH"
DISPLAY_ENV_PREFIX=(
CMUX_UI_TEST_DISPLAY_READY_PATH="$READY_PATH"
CMUX_UI_TEST_DISPLAY_ID_PATH="$DISPLAY_ID_PATH"
CMUX_UI_TEST_DISPLAY_START_PATH="$START_PATH"
CMUX_UI_TEST_DISPLAY_DONE_PATH="$DONE_PATH"
CMUX_UI_TEST_DISPLAY_LOG_PATH="$LOG_PATH"
)
{"helperBinaryPath":"$HELPER_PATH"}
EOF
printf '{"helperBinaryPath":"%s"}\n' "$HELPER_PATH" >"$MANIFEST_PATH"
Fix with Cubic

fi

# Start recording right before the test (after build/resolve).
Expand Down
10 changes: 5 additions & 5 deletions cmuxTests/AppDelegateShortcutRoutingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import XCTest
@testable import cmux
#endif

private let lastSurfaceCloseShortcutDefaultsKey = "closeWorkspaceOnLastSurfaceShortcut"
private let appDelegateLastSurfaceCloseShortcutDefaultsKey = "closeWorkspaceOnLastSurfaceShortcut"

@MainActor
final class AppDelegateShortcutRoutingTests: XCTestCase {
Expand Down Expand Up @@ -725,13 +725,13 @@ final class AppDelegateShortcutRoutingTests: XCTestCase {
}

let defaults = UserDefaults.standard
let originalSetting = defaults.object(forKey: lastSurfaceCloseShortcutDefaultsKey)
defaults.set(false, forKey: lastSurfaceCloseShortcutDefaultsKey)
let originalSetting = defaults.object(forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey)
defaults.set(false, forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey)
defer {
if let originalSetting {
defaults.set(originalSetting, forKey: lastSurfaceCloseShortcutDefaultsKey)
defaults.set(originalSetting, forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey)
} else {
defaults.removeObject(forKey: lastSurfaceCloseShortcutDefaultsKey)
defaults.removeObject(forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey)
}
}

Expand Down
37 changes: 0 additions & 37 deletions cmuxTests/BrowserConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2737,43 +2737,6 @@ final class CmuxWebViewDragRoutingTests: XCTestCase {
}
}

#if compiler(>=6.2)
@available(macOS 26.0, *)
private struct DragConfigurationOperationsSnapshot: Equatable {
let allowCopy: Bool
let allowMove: Bool
let allowDelete: Bool
let allowAlias: Bool
}

@available(macOS 26.0, *)
private enum DragConfigurationSnapshotError: Error {
case missingBoolField(primary: String, fallback: String?)
}

@available(macOS 26.0, *)
private func dragConfigurationOperationsSnapshot<T>(from operations: T) throws -> DragConfigurationOperationsSnapshot {
let mirror = Mirror(reflecting: operations)

func readBool(_ primary: String, fallback: String? = nil) throws -> Bool {
if let value = mirror.descendant(primary) as? Bool {
return value
}
if let fallback, let value = mirror.descendant(fallback) as? Bool {
return value
}
throw DragConfigurationSnapshotError.missingBoolField(primary: primary, fallback: fallback)
}

return try DragConfigurationOperationsSnapshot(
allowCopy: readBool("allowCopy", fallback: "_allowCopy"),
allowMove: readBool("allowMove", fallback: "_allowMove"),
allowDelete: readBool("allowDelete", fallback: "_allowDelete"),
allowAlias: readBool("allowAlias", fallback: "_allowAlias")
)
}


final class BrowserLinkOpenSettingsTests: XCTestCase {
private var suiteName: String!
private var defaults: UserDefaults!
Expand Down
8 changes: 4 additions & 4 deletions cmuxTests/BrowserPanelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import UserNotifications
@testable import cmux
#endif

func drainMainQueue() {
private func drainBrowserPanelMainQueue() {
let expectation = XCTestExpectation(description: "drain main queue")
DispatchQueue.main.async {
expectation.fulfill()
Expand All @@ -22,7 +22,7 @@ func drainMainQueue() {
}

@MainActor
func makeTemporaryBrowserProfile(named prefix: String) throws -> BrowserProfileDefinition {
private func makeTemporaryBrowserPanelProfile(named prefix: String) throws -> BrowserProfileDefinition {
try XCTUnwrap(
BrowserProfileStore.shared.createProfile(
named: "\(prefix)-\(UUID().uuidString)"
Expand Down Expand Up @@ -107,7 +107,7 @@ final class BrowserPanelOmnibarPillBackgroundColorTests: XCTestCase {
@MainActor
final class BrowserPanelProfileIsolationTests: XCTestCase {
func testStaleDidFinishDoesNotRecordVisitIntoSwitchedProfileHistory() throws {
let alternateProfile = try makeTemporaryBrowserProfile(named: "Switched")
let alternateProfile = try makeTemporaryBrowserPanelProfile(named: "Switched")
let defaultStore = BrowserHistoryStore.shared
let alternateStore = BrowserProfileStore.shared.historyStore(for: alternateProfile.id)
defaultStore.clearHistory()
Expand Down Expand Up @@ -137,7 +137,7 @@ final class BrowserPanelProfileIsolationTests: XCTestCase {
alternateStore.clearHistory()

staleDelegate.webView?(staleWebView, didFinish: nil)
drainMainQueue()
drainBrowserPanelMainQueue()

XCTAssertTrue(
defaultStore.entries.isEmpty,
Expand Down
37 changes: 37 additions & 0 deletions cmuxTests/WindowAndDragTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,42 @@ final class FocusFlashPatternTests: XCTestCase {
}


@available(macOS 26.0, *)
private struct DragConfigurationOperationsSnapshot: Equatable {
let allowCopy: Bool
let allowMove: Bool
let allowDelete: Bool
let allowAlias: Bool
}

@available(macOS 26.0, *)
private enum DragConfigurationSnapshotError: Error {
case missingBoolField(primary: String, fallback: String?)
}

@available(macOS 26.0, *)
private func dragConfigurationOperationsSnapshot<T>(from operations: T) throws -> DragConfigurationOperationsSnapshot {
let mirror = Mirror(reflecting: operations)

func readBool(_ primary: String, fallback: String? = nil) throws -> Bool {
if let value = mirror.descendant(primary) as? Bool {
return value
}
if let fallback, let value = mirror.descendant(fallback) as? Bool {
return value
}
throw DragConfigurationSnapshotError.missingBoolField(primary: primary, fallback: fallback)
}

return try DragConfigurationOperationsSnapshot(
allowCopy: readBool("allowCopy", fallback: "_allowCopy"),
allowMove: readBool("allowMove", fallback: "_allowMove"),
allowDelete: readBool("allowDelete", fallback: "_allowDelete"),
allowAlias: readBool("allowAlias", fallback: "_allowAlias")
)
}

#if compiler(>=6.2)
@MainActor
final class InternalTabDragConfigurationTests: XCTestCase {
func testDisablesExternalOperationsForInternalTabDrags() throws {
Expand Down Expand Up @@ -1080,3 +1116,4 @@ final class MarkdownPanelPointerObserverViewTests: XCTestCase {
XCTAssertNil(overlay.hitTest(NSPoint(x: 40, y: 30)))
}
}
#endif
3 changes: 2 additions & 1 deletion cmuxTests/WorkspaceManualUnreadTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import AppKit
@testable import cmux
#endif

@MainActor
final class WorkspaceManualUnreadTests: XCTestCase {
func testShouldClearManualUnreadWhenFocusMovesToDifferentPanel() {
let previousFocusedPanelId = UUID()
Expand Down Expand Up @@ -281,7 +282,7 @@ final class CommandPaletteSwitcherSearchIndexerTests: XCTestCase {
XCTAssertFalse(keywords.contains("cmd-palette-indexing"))
}

func testSurfaceDetailOutranksWorkspaceDetailForPathToken() {
func testSurfaceDetailOutranksWorkspaceDetailForPathToken() throws {
let metadata = CommandPaletteSwitcherSearchMetadata(
directories: ["/tmp/worktrees/cmux"],
branches: ["feature/cmd-palette"],
Expand Down
1 change: 0 additions & 1 deletion cmuxTests/WorkspaceUnitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1896,4 +1896,3 @@ final class SidebarWorkspaceShortcutHintMetricsTests: XCTestCase {
XCTAssertGreaterThan(widened, base)
}
}
#endif
Loading
Loading