From 68cf29cd2dd5f5229700a3977e60cbcd0c9cae40 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:30:59 -0800 Subject: [PATCH] Deduplicate high-frequency socket metadata updates --- Sources/TerminalController.swift | 91 +++++++++++++++++-- Sources/Workspace.swift | 7 +- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 75 +++++++++++++++ 3 files changed, 161 insertions(+), 12 deletions(-) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 3193df82..4484656e 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -68,6 +68,41 @@ class TerminalController { private init() {} + nonisolated static func shouldReplaceStatusEntry( + current: SidebarStatusEntry?, + key: String, + value: String, + icon: String?, + color: String? + ) -> Bool { + guard let current else { return true } + return current.key != key || current.value != value || current.icon != icon || current.color != color + } + + nonisolated static func shouldReplaceProgress( + current: SidebarProgressState?, + value: Double, + label: String? + ) -> Bool { + guard let current else { return true } + return current.value != value || current.label != label + } + + nonisolated static func shouldReplaceGitBranch( + current: SidebarGitBranchState?, + branch: String, + isDirty: Bool + ) -> Bool { + guard let current else { return true } + return current.branch != branch || current.isDirty != isDirty + } + + nonisolated static func shouldReplacePorts(current: [Int]?, next: [Int]) -> Bool { + let currentSorted = Array(Set(current ?? [])).sorted() + let nextSorted = Array(Set(next)).sorted() + return currentSorted != nextSorted + } + /// Update which window's TabManager receives socket commands. /// This is used when the user switches between multiple terminal windows. func setActiveTabManager(_ tabManager: TabManager?) { @@ -194,7 +229,14 @@ class TerminalController { guard let workspace = tabManager.tabs.first(where: { $0.id == workspaceId }) else { return } let validSurfaceIds = Set(workspace.panels.keys) guard validSurfaceIds.contains(panelId) else { return } - workspace.surfaceListeningPorts[panelId] = ports.isEmpty ? nil : ports + let nextPorts = Array(Set(ports)).sorted() + let currentPorts = workspace.surfaceListeningPorts[panelId] ?? [] + guard currentPorts != nextPorts else { return } + if nextPorts.isEmpty { + workspace.surfaceListeningPorts.removeValue(forKey: panelId) + } else { + workspace.surfaceListeningPorts[panelId] = nextPorts + } workspace.recomputeListeningPorts() } } @@ -10421,12 +10463,22 @@ class TerminalController { result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected" return } + guard Self.shouldReplaceStatusEntry( + current: tab.statusEntries[key], + key: key, + value: value, + icon: icon, + color: color + ) else { + return + } tab.statusEntries[key] = SidebarStatusEntry( key: key, value: value, icon: icon, color: color, - timestamp: Date()) + timestamp: Date() + ) } return result } @@ -10569,6 +10621,9 @@ class TerminalController { result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected" return } + guard Self.shouldReplaceProgress(current: tab.progress, value: clamped, label: label) else { + return + } tab.progress = SidebarProgressState(value: clamped, label: label) } return result @@ -10581,7 +10636,9 @@ class TerminalController { result = "ERROR: Tab not found" return } - tab.progress = nil + if tab.progress != nil { + tab.progress = nil + } } return result } @@ -10599,6 +10656,9 @@ class TerminalController { result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected" return } + guard Self.shouldReplaceGitBranch(current: tab.gitBranch, branch: branch, isDirty: isDirty) else { + return + } tab.gitBranch = SidebarGitBranchState(branch: branch, isDirty: isDirty) } return result @@ -10611,7 +10671,9 @@ class TerminalController { result = "ERROR: Tab not found" return } - tab.gitBranch = nil + if tab.gitBranch != nil { + tab.gitBranch = nil + } } return result } @@ -10628,6 +10690,7 @@ class TerminalController { } ports.append(port) } + let normalizedPorts = Array(Set(ports)).sorted() var result = "OK" DispatchQueue.main.sync { @@ -10664,7 +10727,11 @@ class TerminalController { return } - tab.surfaceListeningPorts[surfaceId] = ports + guard Self.shouldReplacePorts(current: tab.surfaceListeningPorts[surfaceId], next: normalizedPorts) else { + return + } + + tab.surfaceListeningPorts[surfaceId] = normalizedPorts tab.recomputeListeningPorts() } return result @@ -10744,11 +10811,15 @@ class TerminalController { result = "ERROR: Panel not found '\(surfaceId.uuidString)'" return } - tab.surfaceListeningPorts.removeValue(forKey: surfaceId) + if tab.surfaceListeningPorts.removeValue(forKey: surfaceId) != nil { + tab.recomputeListeningPorts() + } } else { - tab.surfaceListeningPorts.removeAll() + if !tab.surfaceListeningPorts.isEmpty { + tab.surfaceListeningPorts.removeAll() + tab.recomputeListeningPorts() + } } - tab.recomputeListeningPorts() } return result } @@ -10792,6 +10863,7 @@ class TerminalController { return } + guard tab.surfaceTTYNames[surfaceId] != ttyName else { return } tab.surfaceTTYNames[surfaceId] = ttyName PortScanner.shared.registerTTY(workspaceId: tab.id, panelId: surfaceId, ttyName: ttyName) } @@ -10799,15 +10871,14 @@ class TerminalController { } private func portsKick(_ args: String) -> String { + let parsed = parseOptions(args) var result = "OK" DispatchQueue.main.sync { guard let tab = resolveTabForReport(args) else { - let parsed = parseOptions(args) result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected" return } - let parsed = parseOptions(args) let panelArg = parsed.options["panel"] ?? parsed.options["surface"] let surfaceId: UUID if let panelArg { diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 54b8b203..092ed8c7 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -559,7 +559,7 @@ final class Workspace: Identifiable, ObservableObject { panelDirectories[panelId] = trimmed } // Update current directory if this is the focused panel - if panelId == focusedPanelId { + if panelId == focusedPanelId, currentDirectory != trimmed { currentDirectory = trimmed } } @@ -616,7 +616,10 @@ final class Workspace: Identifiable, ObservableObject { func recomputeListeningPorts() { let unique = Set(surfaceListeningPorts.values.flatMap { $0 }) - listeningPorts = unique.sorted() + let next = unique.sorted() + if listeningPorts != next { + listeningPorts = next + } } // MARK: - Panel Operations diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 548a979c..977cfa5a 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -3008,3 +3008,78 @@ final class BrowserHostWhitelistTests: XCTestCase { XCTAssertTrue(BrowserLinkOpenSettings.hostMatchesWhitelist("xn--bcher-kva.example", defaults: defaults)) } } + +final class TerminalControllerSidebarDedupeTests: XCTestCase { + func testShouldReplaceStatusEntryReturnsFalseForUnchangedPayload() { + let current = SidebarStatusEntry( + key: "agent", + value: "idle", + icon: "bolt", + color: "#ffffff", + timestamp: Date(timeIntervalSince1970: 123) + ) + XCTAssertFalse( + TerminalController.shouldReplaceStatusEntry( + current: current, + key: "agent", + value: "idle", + icon: "bolt", + color: "#ffffff" + ) + ) + } + + func testShouldReplaceStatusEntryReturnsTrueWhenValueChanges() { + let current = SidebarStatusEntry( + key: "agent", + value: "idle", + icon: "bolt", + color: "#ffffff", + timestamp: Date(timeIntervalSince1970: 123) + ) + XCTAssertTrue( + TerminalController.shouldReplaceStatusEntry( + current: current, + key: "agent", + value: "running", + icon: "bolt", + color: "#ffffff" + ) + ) + } + + func testShouldReplaceProgressReturnsFalseForUnchangedPayload() { + XCTAssertFalse( + TerminalController.shouldReplaceProgress( + current: SidebarProgressState(value: 0.42, label: "indexing"), + value: 0.42, + label: "indexing" + ) + ) + } + + func testShouldReplaceGitBranchReturnsFalseForUnchangedPayload() { + XCTAssertFalse( + TerminalController.shouldReplaceGitBranch( + current: SidebarGitBranchState(branch: "main", isDirty: true), + branch: "main", + isDirty: true + ) + ) + } + + func testShouldReplacePortsIgnoresOrderAndDuplicates() { + XCTAssertFalse( + TerminalController.shouldReplacePorts( + current: [9229, 3000], + next: [3000, 9229, 3000] + ) + ) + XCTAssertTrue( + TerminalController.shouldReplacePorts( + current: [9229, 3000], + next: [3000] + ) + ) + } +}