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
11 changes: 2 additions & 9 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11731,17 +11731,10 @@ private struct TabItemView: View, Equatable {
}

private func directorySummaryText(orderedPanelIds: [UUID]) -> String? {
guard !tab.panels.isEmpty else { return nil }
let home = SidebarPathFormatter.homeDirectoryPath
var seen: Set<String> = []
var entries: [String] = []
for panelId in orderedPanelIds {
let directory = tab.panelDirectories[panelId] ?? tab.currentDirectory
let entries = tab.sidebarDirectoriesInDisplayOrder(orderedPanelIds: orderedPanelIds).compactMap { directory in
let shortened = SidebarPathFormatter.shortenedPath(directory, homeDirectoryPath: home)
guard !shortened.isEmpty else { continue }
if seen.insert(shortened).inserted {
entries.append(shortened)
}
return shortened.isEmpty ? nil : shortened
}
return entries.isEmpty ? nil : entries.joined(separator: " | ")
}
Expand Down
67 changes: 64 additions & 3 deletions Sources/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4739,7 +4739,7 @@ enum SidebarBranchOrdering {
for panelId in orderedPanelIds {
let panelBranch = normalized(panelBranches[panelId]?.branch)
let branch = panelBranch ?? defaultBranchForPanels
let directory = normalized(panelDirectories[panelId] ?? defaultDirectory)
let directory = normalized(panelDirectories[panelId])
guard branch != nil || directory != nil else { continue }

let panelDirty = panelBranch != nil
Expand Down Expand Up @@ -5933,6 +5933,67 @@ final class Workspace: Identifiable, ObservableObject {
)
}

private func normalizedSidebarDirectory(_ directory: String?) -> String? {
guard let directory else { return nil }
let trimmed = directory.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}

private func canonicalSidebarDirectoryKey(_ directory: String?) -> String? {
guard let directory = normalizedSidebarDirectory(directory) else { return nil }
let expanded = NSString(string: directory).expandingTildeInPath
let standardized = NSString(string: expanded).standardizingPath
let cleaned = standardized.trimmingCharacters(in: .whitespacesAndNewlines)
return cleaned.isEmpty ? nil : cleaned
Comment on lines +5942 to +5947

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

Make directory canonicalization remote-aware.

Line 5944 expands ~ against the local machine. In a remote workspace, that makes a requested cwd like ~/repo canonicalize differently from the later live cwd /home/remoteuser/repo, so the sidebar can still show duplicate entries for the same remote directory.

Suggested fix
     private func canonicalSidebarDirectoryKey(_ directory: String?) -> String? {
         guard let directory = normalizedSidebarDirectory(directory) else { return nil }
-        let expanded = NSString(string: directory).expandingTildeInPath
-        let standardized = NSString(string: expanded).standardizingPath
+        let pathForKey = isRemoteWorkspace
+            ? directory
+            : NSString(string: directory).expandingTildeInPath
+        let standardized = NSString(string: pathForKey).standardizingPath
         let cleaned = standardized.trimmingCharacters(in: .whitespacesAndNewlines)
         return cleaned.isEmpty ? nil : cleaned
     }

Please apply the same remote-aware rule to the matching canonicalizer inside SidebarBranchOrdering.orderedUniqueBranchDirectoryEntries(...) so the branch rows and plain directory summary stay consistent.

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

In `@Sources/Workspace.swift` around lines 5942 - 5947, The current
canonicalization uses NSString.expandingTildeInPath which expands ~ against the
local machine; update canonicalSidebarDirectoryKey(_:) and the matching
canonicalizer inside
SidebarBranchOrdering.orderedUniqueBranchDirectoryEntries(...) to perform tilde
expansion against the workspace's remote/home context when the workspace is
remote (use the workspace/connection's remoteHome or live CWD to resolve "~"),
then continue to standardizePath and trim whitespace as before so both sidebar
keys and branch-directory entries canonicalize identically in remote workspaces.

}

private func sidebarResolvedDirectory(for panelId: UUID) -> String? {
if let directory = normalizedSidebarDirectory(panelDirectories[panelId]) {
return directory
}
if let requestedDirectory = normalizedSidebarDirectory(
terminalPanel(for: panelId)?.requestedWorkingDirectory
) {
return requestedDirectory
}
guard panelId == focusedPanelId else { return nil }
return normalizedSidebarDirectory(currentDirectory)
}

private func sidebarResolvedPanelDirectories(orderedPanelIds: [UUID]) -> [UUID: String] {
var resolved: [UUID: String] = [:]
for panelId in orderedPanelIds {
if let directory = sidebarResolvedDirectory(for: panelId) {
resolved[panelId] = directory
}
}
return resolved
}

func sidebarDirectoriesInDisplayOrder(orderedPanelIds: [UUID]) -> [String] {
let resolvedDirectories = sidebarResolvedPanelDirectories(orderedPanelIds: orderedPanelIds)
var ordered: [String] = []
var seen: Set<String> = []

for panelId in orderedPanelIds {
guard let directory = resolvedDirectories[panelId],
let key = canonicalSidebarDirectoryKey(directory) else { continue }
if seen.insert(key).inserted {
ordered.append(directory)
}
}

if ordered.isEmpty, let fallbackDirectory = normalizedSidebarDirectory(currentDirectory) {
return [fallbackDirectory]
}

return ordered
}

func sidebarDirectoriesInDisplayOrder() -> [String] {
sidebarDirectoriesInDisplayOrder(orderedPanelIds: sidebarOrderedPanelIds())
}

func sidebarGitBranchesInDisplayOrder(orderedPanelIds: [UUID]) -> [SidebarGitBranchState] {
SidebarBranchOrdering
.orderedUniqueBranches(
Expand All @@ -5953,8 +6014,8 @@ final class Workspace: Identifiable, ObservableObject {
SidebarBranchOrdering.orderedUniqueBranchDirectoryEntries(
orderedPanelIds: orderedPanelIds,
panelBranches: panelGitBranches,
panelDirectories: panelDirectories,
defaultDirectory: currentDirectory,
panelDirectories: sidebarResolvedPanelDirectories(orderedPanelIds: orderedPanelIds),
defaultDirectory: normalizedSidebarDirectory(currentDirectory),
fallbackBranch: gitBranch
)
}
Expand Down
53 changes: 53 additions & 0 deletions cmuxTests/WorkspaceUnitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,59 @@ final class WorkspacePanelGitBranchTests: XCTestCase {
XCTAssertEqual(branches.map(\.isDirty), [true, false, false])
}

func testSidebarBranchDirectoryEntriesStayStableAcrossFocusedSplitChanges() {
let workspace = Workspace()
let leftLiveDirectory = "/repo/left/live"
let rightFocusedDirectory = "/repo/right/focused"
let leftFocusedDirectory = "/repo/left/focused"
let rightRequestedDirectory = "/repo/right/requested"

guard let leftPanelId = workspace.focusedPanelId else {
XCTFail("Expected initial focused panel")
return
}

workspace.updatePanelDirectory(panelId: leftPanelId, directory: leftLiveDirectory)

guard let rightSplitPanel = workspace.newTerminalSplit(
from: leftPanelId,
orientation: .horizontal,
focus: false
),
let rightPaneId = workspace.paneId(forPanelId: rightSplitPanel.id),
let rightRequestedPanel = workspace.newTerminalSurface(
inPane: rightPaneId,
focus: false,
workingDirectory: rightRequestedDirectory
) else {
XCTFail("Expected right split panes for sidebar directory ordering test")
return
}

let orderedPanelIds = workspace.sidebarOrderedPanelIds()
XCTAssertEqual(orderedPanelIds, [leftPanelId, rightSplitPanel.id, rightRequestedPanel.id])

workspace.currentDirectory = rightFocusedDirectory
let entriesWhenRightLooksFocused = workspace.sidebarBranchDirectoryEntriesInDisplayOrder(
orderedPanelIds: orderedPanelIds
)

workspace.currentDirectory = leftFocusedDirectory
let entriesWhenLeftLooksFocused = workspace.sidebarBranchDirectoryEntriesInDisplayOrder(
orderedPanelIds: orderedPanelIds
)

XCTAssertEqual(
entriesWhenRightLooksFocused,
entriesWhenLeftLooksFocused,
"Expected sidebar directory ordering to ignore focused-workspace cwd churn when panel-specific directories are available"
)
XCTAssertEqual(
entriesWhenRightLooksFocused.map(\.directory),
[leftLiveDirectory, rightRequestedDirectory]
)
}

func testSidebarDerivedCollectionsMatchWhenUsingPrecomputedPanelOrder() {
let workspace = Workspace()
guard let leftFirstPanelId = workspace.focusedPanelId,
Expand Down
Loading