Stabilize sidebar directory ordering when split focus changes (#1798)

* Add sidebar directory ordering regression test

* Stabilize sidebar directory ordering

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
This commit is contained in:
Lawrence Chen 2026-03-19 01:05:12 -07:00 committed by GitHub
parent 7cbac356fc
commit 0010e10bf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 119 additions and 12 deletions

View file

@ -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: " | ")
}

View file

@ -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
@ -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
}
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(
@ -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
)
}

View file

@ -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,