Skip to content
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6892147
Replace vendor bonsplit package with local PaneKit
lawrencecchen Mar 14, 2026
9f728d8
Add paper-canvas pane layout engine
lawrencecchen Mar 14, 2026
2879bbb
Persist and restore paper canvas layouts
lawrencecchen Mar 14, 2026
d28a8b0
Fix paper canvas restore focus mapping
lawrencecchen Mar 14, 2026
0edf0e9
Make paper canvas the default pane layout
lawrencecchen Mar 14, 2026
43bd589
Implement and stabilize the paper canvas pane strip
lawrencecchen Mar 17, 2026
7b57c2d
Pin GhosttyKit checksum for pane strip motion fix
lawrencecchen Mar 17, 2026
ad7d02f
Add pane strip UI tests to Xcode target
lawrencecchen Mar 18, 2026
e08a636
Keep pane strip UI harness alive under XCUITest
lawrencecchen Mar 18, 2026
4a4e639
Relax pane strip UI bootstrap grace
lawrencecchen Mar 18, 2026
044f287
Tighten pane-strip portal visibility sync
lawrencecchen Mar 18, 2026
3dfef9c
Fix pane-strip terminal visibility during animated pane changes
lawrencecchen Mar 18, 2026
1051815
Fix pane-strip UI test activation on CI
lawrencecchen Mar 18, 2026
01deaad
test: cover paper pane-strip middle-close width stability
lawrencecchen Mar 18, 2026
a8de599
fix: prewarm pane strip focus reveal harness
lawrencecchen Mar 18, 2026
0cc9db3
fix: stabilize pane strip ui test launch
lawrencecchen Mar 18, 2026
60495a1
fix: relax pane strip runtime launch readiness
lawrencecchen Mar 18, 2026
25e846a
test: stabilize pane strip motion activation
lawrencecchen Mar 18, 2026
c02f40a
Add pane-strip motion verification coverage
lawrencecchen Mar 18, 2026
e100603
Fix pane-strip browser sync and UI test launch
lawrencecchen Mar 18, 2026
cca0db2
test: assert pane strip terminal visibility on screen
lawrencecchen Mar 18, 2026
73aab33
Add pane-strip regression for browser split blanking source terminal
lawrencecchen Mar 18, 2026
d6dd3a8
Keep browser and terminal portals synced during pane-structure changes
lawrencecchen Mar 18, 2026
21ee3bb
Stabilize pane-strip UI test app activation
lawrencecchen Mar 18, 2026
28a5022
test: stop priming pane-strip startup terminal
lawrencecchen Mar 19, 2026
a654d96
fix: bootstrap blank pane-strip terminals when visible
lawrencecchen Mar 19, 2026
b6ef23a
fix: tick ghostty during terminal bootstrap refresh
lawrencecchen Mar 19, 2026
85fc810
Refresh blank terminals after activation
lawrencecchen Mar 19, 2026
468122b
Stabilize E2E UI test logging
lawrencecchen Mar 19, 2026
edc6dba
Add pane-strip regressions for blank hosted terminals
lawrencecchen Mar 19, 2026
5098653
Replay hosted terminal visibility after surface creation
lawrencecchen Mar 19, 2026
9b7f64d
Make pane-strip motion harness use real shortcut flow
lawrencecchen Mar 19, 2026
daa8b5b
Upload xcresult from E2E runs
lawrencecchen Mar 19, 2026
8dadf89
test: reproduce pane strip late-activation blank terminal
lawrencecchen Mar 20, 2026
2980fd4
fix: replay pane portal recovery after app activation
lawrencecchen Mar 20, 2026
7214bed
fix: make pane strip late activation explicit
lawrencecchen Mar 20, 2026
a0e32b2
test: expose pane strip cold-start regression
lawrencecchen Mar 21, 2026
f2774ce
fix: bootstrap pane strip terminals on real host readiness
lawrencecchen Mar 21, 2026
5629437
fix: add explicit pane-strip motion diagnostics init
lawrencecchen Mar 21, 2026
ffa9d54
fix: keep pane strip browsers aligned with pane motion
lawrencecchen Mar 21, 2026
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
23 changes: 18 additions & 5 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ jobs:
set -euo pipefail
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
ONLY_TESTING="-only-testing:cmuxUITests/$TEST_FILTER"
OUTPUT_FILE="$(mktemp -t cmux-e2e-output.XXXXXX)"
RESULT_BUNDLE="/tmp/cmux-ui-tests.xcresult"
trap 'rm -f "$OUTPUT_FILE"' EXIT
rm -rf "$RESULT_BUNDLE"

# Start recording right before the test (after build/resolve)
if [ "$RECORD_VIDEO" = "true" ]; then
Expand Down Expand Up @@ -216,19 +220,20 @@ jobs:
fi

set +e
OUTPUT=$(xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
-disableAutomaticPackageResolution \
-destination "platform=macOS" \
-maximum-test-execution-time-allowance "$TEST_TIMEOUT" \
$ONLY_TESTING test 2>&1)
-resultBundlePath "$RESULT_BUNDLE" \
$ONLY_TESTING test >"$OUTPUT_FILE" 2>&1
EXIT_CODE=$?
set -e

echo "$OUTPUT"
cat "$OUTPUT_FILE"

# Save summary for the issue
SUMMARY=$(echo "$OUTPUT" | grep -E "(Test Suite|Executed|FAIL|PASS)" | tail -20)
SUMMARY=$(grep -E "(Test Suite|Executed|FAIL|PASS)" "$OUTPUT_FILE" | tail -20 || true)
{
echo "test_summary<<EOFSUM"
echo "$SUMMARY"
Expand All @@ -242,7 +247,7 @@ jobs:
# Save full output for the issue body
{
echo "test_output<<EOFOUT"
echo "$OUTPUT" | tail -200
tail -200 "$OUTPUT_FILE"
echo "EOFOUT"
} >> "$GITHUB_OUTPUT"
exit 1
Expand Down Expand Up @@ -297,6 +302,14 @@ jobs:
path: /tmp/test-recording.mp4
if-no-files-found: warn

- name: Upload xcresult artifact
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ui-test-xcresult
path: /tmp/cmux-ui-tests.xcresult
if-no-files-found: warn

- name: Post results to cmux-dev-artifacts
if: always()
env:
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@
[submodule "homebrew-cmux"]
path = homebrew-cmux
url = https://github.com/manaflow-ai/homebrew-cmux.git
[submodule "vendor/bonsplit"]
path = vendor/bonsplit
url = https://github.com/manaflow-ai/bonsplit.git
56 changes: 56 additions & 0 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,38 @@ struct CMUXCLI {
print(response)
}

case "pan-workspace":
let workspaceArg = workspaceFromArgsOrEnv(commandArgs, windowOverride: windowId)
let dxRaw = optionValue(commandArgs, name: "--dx")
let dyRaw = optionValue(commandArgs, name: "--dy")
let dx: Int
if let dxRaw {
guard let parsed = Int(dxRaw) else {
throw CLIError(message: String(localized: "cli.pan-workspace.error.dxInteger", defaultValue: "--dx must be an integer"))
}
dx = parsed
} else {
dx = 0
}
let dy: Int
if let dyRaw {
guard let parsed = Int(dyRaw) else {
throw CLIError(message: String(localized: "cli.pan-workspace.error.dyInteger", defaultValue: "--dy must be an integer"))
}
dy = parsed
} else {
dy = 0
}
guard dx != 0 || dy != 0 else {
throw CLIError(message: String(localized: "cli.pan-workspace.error.requiresDelta", defaultValue: "pan-workspace requires a non-zero --dx and/or --dy"))
}

var params: [String: Any] = ["dx": dx, "dy": dy]
let wsId = try normalizeWorkspaceHandle(workspaceArg, client: client, allowCurrent: true)
if let wsId { params["workspace_id"] = wsId }
let payload = try client.sendV2(method: "workspace.viewport.pan", params: params)
printV2Payload(payload, jsonOutput: jsonOutput, idFormat: idFormat, fallbackText: v2OKSummary(payload, idFormat: idFormat, kinds: ["workspace"]))

case "read-screen":
let (wsArg, rem0) = parseOption(commandArgs, name: "--workspace")
let (sfArg, rem1) = parseOption(rem0, name: "--surface")
Expand Down Expand Up @@ -4522,6 +4554,9 @@ struct CMUXCLI {

Split the current pane in the given direction.

Note:
Horizontal pane-strip workspaces reject up/down splits with not_supported.

Flags:
--workspace <id|ref> Target workspace (default: $CMUX_WORKSPACE_ID)
--surface <id|ref> Surface to split from (default: $CMUX_SURFACE_ID)
Expand Down Expand Up @@ -4766,6 +4801,26 @@ struct CMUXCLI {

Print the currently selected workspace ID.
"""
case "pan-workspace":
return String(localized: "cli.pan-workspace.usage", defaultValue: """
Usage: cmux pan-workspace [--workspace <id|ref|index>] [--dx <pixels>] [--dy <pixels>]

Pan the paper-canvas viewport for a workspace.

Flags:
--workspace <id|ref|index> Workspace to pan (default: current/$CMUX_WORKSPACE_ID)
--dx <pixels> Horizontal pan delta in pixels
--dy <pixels> Vertical pan delta in pixels

Notes:
Positive --dx pans right, negative pans left.
Positive --dy pans down, negative pans up.
At least one of --dx or --dy must be non-zero.

Example:
cmux pan-workspace --dx 400
cmux pan-workspace --workspace workspace:2 --dx -240 --dy 180
""")
case "capture-pane":
return """
Usage: cmux capture-pane [--workspace <id|ref>] [--surface <id|ref>] [--scrollback] [--lines <n>]
Expand Down Expand Up @@ -8412,6 +8467,7 @@ struct CMUXCLI {
workspace-action --action <name> [--workspace <id|ref|index>] [--title <text>]
list-workspaces
new-workspace [--cwd <path>] [--command <text>]
pan-workspace [--workspace <id|ref|index>] [--dx <pixels>] [--dy <pixels>]
new-split <left|right|up|down> [--workspace <id|ref>] [--surface <id|ref>] [--panel <id|ref>]
list-panes [--workspace <id|ref>]
list-pane-surfaces [--workspace <id|ref>] [--pane <id|ref>]
Expand Down
18 changes: 11 additions & 7 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
B8F266236A1A3D9A45BD840F /* SidebarResizeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */; };
B8F266246A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F266256A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift */; };
C0B4D9B0A1B2C3D4E5F60718 /* UpdatePillUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */; };
E2000010A1B2C3D4E5F60718 /* PaneStripUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2000011A1B2C3D4E5F60718 /* PaneStripUITests.swift */; };
B9000014A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000013A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift */; };
B9000015A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000016A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift */; };
B900001AA1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000019A1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift */; };
Expand Down Expand Up @@ -213,6 +214,7 @@
818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarResizeUITests.swift; sourceTree = "<group>"; };
B8F266256A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarHelpMenuUITests.swift; sourceTree = "<group>"; };
C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatePillUITests.swift; sourceTree = "<group>"; };
E2000011A1B2C3D4E5F60718 /* PaneStripUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneStripUITests.swift; sourceTree = "<group>"; };
A5001101 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
IC000002 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = AppIcon.icon; sourceTree = "<group>"; };
B2E7294509CC42FE9191870E /* xterm-ghostty */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty/terminfo/78/xterm-ghostty"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -457,6 +459,7 @@
D0E0F0B1A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift */,
D0E0F0B3A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift */,
C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */,
E2000011A1B2C3D4E5F60718 /* PaneStripUITests.swift */,
E1000001A1B2C3D4E5F60718 /* MenuKeyEquivalentRoutingUITests.swift */,
);
path = cmuxUITests;
Expand Down Expand Up @@ -504,7 +507,7 @@
A5001231 /* Sparkle */,
A5001251 /* Sentry */,
A5001271 /* PostHog */,
A5001261 /* Bonsplit */,
A5001261 /* PaneKit */,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
from pathlib import Path
import re

proj = Path("GhosttyTabs.xcodeproj/project.pbxproj")
text = proj.read_text()

target = re.search(
    r'A5001050 /\* GhosttyTabs \*/ = \{.*?packageProductDependencies = \((.*?)\);\s*name = GhosttyTabs;',
    text,
    re.S,
)
frameworks = re.search(
    r'A5001030 /\* Frameworks \*/ = \{.*?files = \((.*?)\);\s*runOnlyForDeploymentPostprocessing = 0;\s*\};',
    text,
    re.S,
)

deps = re.findall(r'/\* ([^*]+) \*/', target.group(1))
linked = re.findall(r'/\* ([^*]+) in Frameworks \*/', frameworks.group(1))
missing = [dep for dep in deps if dep not in linked]
pane_buildfiles = len(re.findall(r'productRef = .*?/\* PaneKit \*/', text))

print("GhosttyTabs packageProductDependencies:", deps)
print("GhosttyTabs Frameworks build phase:", linked)
print("Missing framework links:", missing)
print("PaneKit PBXBuildFile entries:", pane_buildfiles)
PY

Repository: manaflow-ai/cmux

Length of output: 335


Fix incomplete PaneKit linking in GhosttyTabs target.

PaneKit is declared in packageProductDependencies but is missing from the Frameworks build phase and has no PBXBuildFile entries. Unlike Sparkle, Sentry, PostHog, and MarkdownUI, the framework is not fully wired up. Re-adding PaneKit to the target in Xcode will regenerate the required build file entries.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GhosttyTabs.xcodeproj/project.pbxproj` at line 510, PaneKit is listed in
packageProductDependencies but not actually wired into the GhosttyTabs target
(missing Frameworks build phase entries and PBXBuildFile records); open the
GhosttyTabs target in Xcode and re-add the PaneKit package product to the
target’s Frameworks (or Link Binary With Libraries) phase so Xcode regenerates
the missing PBXBuildFile entries and framework linkage; verify PaneKit appears
alongside Sparkle/Sentry/PostHog/MarkdownUI in the Frameworks build phase and
that corresponding PBXBuildFile and fileReference entries are present in the
project.pbxproj.

A5001291 /* MarkdownUI */,
);
name = GhosttyTabs;
Expand Down Expand Up @@ -608,7 +611,7 @@
A5001252 /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
A5001272 /* XCRemoteSwiftPackageReference "posthog-ios" */,
A5001292 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
A5001260 /* XCLocalSwiftPackageReference "bonsplit" */,
A5001260 /* XCLocalSwiftPackageReference "PaneKit" */,
);
productRefGroup = A5001042 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -697,6 +700,7 @@
D0E0F0B0A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift in Sources */,
D0E0F0B2A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift in Sources */,
C0B4D9B0A1B2C3D4E5F60718 /* UpdatePillUITests.swift in Sources */,
E2000010A1B2C3D4E5F60718 /* PaneStripUITests.swift in Sources */,
E1000000A1B2C3D4E5F60718 /* MenuKeyEquivalentRoutingUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1034,9 +1038,9 @@
minimumVersion = 2.4.1;
};
};
A5001260 /* XCLocalSwiftPackageReference "bonsplit" */ = {
A5001260 /* XCLocalSwiftPackageReference "PaneKit" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = vendor/bonsplit;
relativePath = PaneKit;
};
/* End XCRemoteSwiftPackageReference section */

Expand All @@ -1056,10 +1060,10 @@
package = A5001272 /* XCRemoteSwiftPackageReference "posthog-ios" */;
productName = PostHog;
};
A5001261 /* Bonsplit */ = {
A5001261 /* PaneKit */ = {
isa = XCSwiftPackageProductDependency;
package = A5001260 /* XCLocalSwiftPackageReference "bonsplit" */;
productName = Bonsplit;
package = A5001260 /* XCLocalSwiftPackageReference "PaneKit" */;
productName = PaneKit;
};
A5001291 /* MarkdownUI */ = {
isa = XCSwiftPackageProductDependency;
Expand Down
28 changes: 28 additions & 0 deletions PaneKit/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "PaneKit",
platforms: [
.macOS(.v14)
],
products: [
.library(
name: "PaneKit",
targets: ["PaneKit"]
),
],
targets: [
.target(
name: "PaneKit",
dependencies: [],
path: "Sources/PaneKit"
),
.testTarget(
name: "PaneKitTests",
dependencies: ["PaneKit"],
path: "Tests/PaneKitTests"
),
]
)
Loading
Loading