Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
52e2827
Add remote tmux mirror title regression tests
austinywang Jul 10, 2026
2edd0aa
Use tmux window names for mirror pane titles
austinywang Jul 10, 2026
0d0e847
Fix mirror title test actor isolation
austinywang Jul 10, 2026
5291d70
Address mirror title review feedback
austinywang Jul 10, 2026
46654ff
Fix swift package CI helper Xcode selection
austinywang Jul 10, 2026
65aa24c
Update Korean tmux window localization
austinywang Jul 10, 2026
58081e7
Merge remote-tracking branch 'origin/main' into issue-7832-mirror-win…
austinywang Jul 10, 2026
5b5ff17
Update swift package helper CI guards
austinywang Jul 10, 2026
58931a9
Fix mirror title test imports and restore CI SDK contract
austinywang Jul 10, 2026
5a0a427
Reject duplicate remote tmux pane IDs
austinywang Jul 10, 2026
9d0a340
Route package tests to dual-Xcode runner
austinywang Jul 10, 2026
bce80a4
Localize indexed mirror pane titles
austinywang Jul 10, 2026
a2e6bac
Make mirror title rect replies deterministic
austinywang Jul 10, 2026
a306810
Document dual-Xcode CI runner
austinywang Jul 10, 2026
615e2da
Drain mirror seed replies in title tests
austinywang Jul 10, 2026
ff7349f
Merge branch 'main' of https://github.com/manaflow-ai/cmux into issue…
austinywang Jul 10, 2026
0cab431
Make node-dependent wrapper tests resilient to runner PATH drift
austinywang Jul 10, 2026
b9ec236
ci: guarantee a node runtime for the wrapper regression shard
austinywang Jul 10, 2026
3f472d3
ci: match the repo's pinned setup-node version
austinywang Jul 10, 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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,8 @@ jobs:
# transitive skip otherwise marks every macOS job skipped even when
# linux-preflight itself succeeds. Require the direct needs explicitly.
if: ${{ !cancelled() && needs.changes.result == 'success' && needs.linux-preflight.result == 'success' && needs.changes.outputs.macos == 'true' }}
runs-on: ${{ vars.MACOS_RUNNER_15 || 'warp-macos-15-arm64-6x' }}
# Build the release helper with SDK 15, then run package tests with SDK 26.
runs-on: ${{ vars.MACOS_RUNNER_DUAL_XCODE || 'blacksmith-6vcpu-macos-15' }}
timeout-minutes: 40
env:
CMUX_CI_XCODE_APP: ${{ vars.CMUX_CI_XCODE_APP_MACOS_15 }}
Expand Down
233 changes: 233 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -132873,17 +132873,250 @@
"remoteTmux.tab.window": {
"extractionState": "manual",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "نافذة tmux"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "tmux prozor"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "tmux-vindue"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "tmux-Fenster"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "tmux window"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "ventana de tmux"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "fenêtre tmux"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "finestra tmux"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "tmux ウィンドウ"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "បង្អួច tmux"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "tmux 윈도우"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "tmux-vindu"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "okno tmux"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "janela tmux"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "окно tmux"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "หน้าต่าง tmux"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "tmux penceresi"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "вікно tmux"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "tmux 窗口"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "tmux 視窗"
}
}
}
},
"remoteTmux.tab.windowPaneIndexed": {
"extractionState": "manual",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "%1$@ ‏[%2$@]"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%1$@[%2$@]"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "%1$@ [%2$@]"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "%1$@[%2$@]"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "%1$@[%2$@]"
}
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions Sources/RemoteTmuxRawLayoutParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ enum RemoteTmuxRawLayoutParser {
}
var cursor = 0
guard let node = parseNode(chars, &cursor), cursor == chars.count else { return nil }
let paneIDs = node.paneIDsInOrder
guard Set(paneIDs).count == paneIDs.count else { return nil }
return node
}

Expand Down
6 changes: 2 additions & 4 deletions Sources/RemoteTmuxWindowMirror+Bonsplit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,8 @@ extension RemoteTmuxWindowMirror {
}

func title(forPane paneId: Int) -> String {
Self.paneTitle(
command: connection?.paneForegroundStates[paneId]?.command,
cwd: cwdByPaneId[paneId]
) ?? String(localized: "remoteTmux.tab.pane", defaultValue: "tmux pane")
let index = paneIndexByPaneId[paneId] ?? 0
return Self.windowPaneTitle(windowTitle, paneIndex: index)
}

func combined(children: [RemoteTmuxLayoutNode], orientation: SplitOrientation) -> RemoteTmuxLayoutNode {
Expand Down
23 changes: 11 additions & 12 deletions Sources/RemoteTmuxWindowMirror+BonsplitLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,17 @@ extension RemoteTmuxWindowMirror {
).clientGrid(layout: layout, contentSize: contentSize)
}

nonisolated static func paneTitle(command: String?, cwd: String?) -> String? {
let trimmedCommand = command?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedCommand.isEmpty,
!RemoteTmuxPaneForegroundState.plainShellCommands.contains(trimmedCommand) {
return trimmedCommand
}
let trimmedCwd = cwd?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedCwd.isEmpty {
let component = URL(fileURLWithPath: trimmedCwd).lastPathComponent
if !component.isEmpty { return component }
}
return nil
nonisolated static func windowPaneTitle(_ windowTitle: String, paneIndex: Int) -> String {
let trimmed = windowTitle.trimmingCharacters(in: .whitespacesAndNewlines)
let base = trimmed.isEmpty
? String(localized: "remoteTmux.tab.window", defaultValue: "tmux window")
: trimmed
guard paneIndex > 0 else { return base }
let formattedIndex = paneIndex.formatted()
return String(
localized: "remoteTmux.tab.windowPaneIndexed",
defaultValue: "\(base) [\(formattedIndex)]"
)
Comment thread
cursor[bot] marked this conversation as resolved.
}

nonisolated static func dividerFraction(
Expand Down
4 changes: 1 addition & 3 deletions Sources/RemoteTmuxWindowMirror+ControlTopology.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ extension RemoteTmuxWindowMirror {
let panel = panel(forPane: tmuxPaneID) else {
return nil
}
let header = paneHeaderLabels[tmuxPaneID]?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return RemoteTmuxControlPane(
tmuxPaneID: tmuxPaneID,
paneID: paneID,
panel: panel,
title: header.isEmpty ? panel.displayTitle : header,
title: title(forPane: tmuxPaneID),
isFocused: tmuxPaneID == activePaneId
)
}
Expand Down
15 changes: 13 additions & 2 deletions Sources/RemoteTmuxWindowMirror.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ final class RemoteTmuxWindowMirror {
private(set) var layoutStructureVersion = 0
/// The tmux pane the user last focused (drives the focus overlay + splits).
private(set) var activePaneId: Int?
/// Display title for this mirrored tmux window; every inner surface/tab title
/// derives from this tmux window name, never from pane-border labels.
private(set) var windowTitle = String(localized: "remoteTmux.tab.window", defaultValue: "tmux window")

/// Only the visible tab's mirror writes after its initial claim. Hidden
/// tabs stay mounted and still receive geometry callbacks, so default-hidden
Expand All @@ -77,6 +80,7 @@ final class RemoteTmuxWindowMirror {
@ObservationIgnored var paneIdByPaneId: [Int: PaneID] = [:]
@ObservationIgnored var paneIdByBonsplitPane: [PaneID: Int] = [:]
@ObservationIgnored var paneIdByTabId: [TabID: Int] = [:]
@ObservationIgnored var paneIndexByPaneId: [Int: Int] = [:]
@ObservationIgnored var cwdByPaneId: [Int: String] = [:]
@ObservationIgnored var isApplyingRemoteLayout = false
@ObservationIgnored var isApplyingTmuxFocus = false
Expand Down Expand Up @@ -168,6 +172,8 @@ final class RemoteTmuxWindowMirror {
/// never creates or closes panels, and f's output is zoom-invariant.
func apply(window: RemoteTmuxWindow) {
let previousRenderedLayout = renderedLayout
let nextTitle = RemoteTmuxSessionMirror.tabTitle(for: window)
if windowTitle != nextTitle { windowTitle = nextTitle }
let newVisible = window.zoomed ? window.visibleLayout : nil
if visibleLayout != newVisible { visibleLayout = newVisible }
if zoomed != window.zoomed { zoomed = window.zoomed }
Expand All @@ -185,8 +191,13 @@ final class RemoteTmuxWindowMirror {
layout newLayout: RemoteTmuxLayoutNode,
previousRenderedLayout: RemoteTmuxLayoutNode
) {
let livePaneIds = Set(newLayout.paneIDsInOrder)
for paneId in newLayout.paneIDsInOrder where panelsByPaneId[paneId] == nil {
let livePaneIDsInOrder = newLayout.paneIDsInOrder
let livePaneIds = Set(livePaneIDsInOrder)
paneIndexByPaneId = Dictionary(
livePaneIDsInOrder.enumerated().map { ($0.element, $0.offset) },
uniquingKeysWith: { firstIndex, _ in firstIndex }
)
for paneId in livePaneIDsInOrder where panelsByPaneId[paneId] == nil {
Comment thread
cursor[bot] marked this conversation as resolved.
guard let panel = makePanel(paneId) else { continue }
panelsByPaneId[paneId] = panel
let surface = panel.surface
Expand Down
4 changes: 4 additions & 0 deletions cmuxTests/RemoteTmuxControlParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ import Testing
#expect(RemoteTmuxRawLayoutParser.parse("abcd,60x40,0,0{60x40,0,0,4}") == nil)
}

@Test func rejectsDuplicatePaneIDs() {
#expect(RemoteTmuxRawLayoutParser.parse("abcd,120x40,0,0{60x40,0,0,4,59x40,61,0,4}") == nil)
}

@Test func rejectsGarbageLayout() {
#expect(RemoteTmuxRawLayoutParser.parse("not-a-layout") == nil)
#expect(RemoteTmuxRawLayoutParser.parse("") == nil)
Expand Down
5 changes: 0 additions & 5 deletions cmuxTests/RemoteTmuxMirrorLayoutMathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,4 @@ import Testing
#expect(grid?.rows == 5)
}

@Test func paneTitleUsesCommandThenShellCwdThenNil() {
#expect(RemoteTmuxWindowMirror.paneTitle(command: "vim", cwd: "/tmp/project") == "vim")
#expect(RemoteTmuxWindowMirror.paneTitle(command: "zsh", cwd: "/tmp/project") == "project")
#expect(RemoteTmuxWindowMirror.paneTitle(command: "", cwd: nil) == nil)
}
}
Loading
Loading