Scope sidebar row observation to visible workspace state
This commit is contained in:
parent
f82e016067
commit
3666f48a9a
3 changed files with 103 additions and 6 deletions
|
|
@ -11140,7 +11140,7 @@ enum SidebarTrailingAccessoryWidthPolicy {
|
|||
// Reactive workspace state inside the row must not rely on parent diffs alone:
|
||||
// `.equatable()` can otherwise leave sidebar badges/details stale until an
|
||||
// unrelated parent change sneaks through. Keep the workspace reference plain
|
||||
// and bridge its objectWillChange into local state instead.
|
||||
// and bridge only sidebar-visible workspace changes into local state.
|
||||
// Do NOT add @EnvironmentObject or new @Binding without updating ==.
|
||||
// Do NOT remove .equatable() from the ForEach call site in VerticalTabsSidebar.
|
||||
private struct TabItemView: View, Equatable {
|
||||
|
|
@ -11772,7 +11772,7 @@ private struct TabItemView: View, Equatable {
|
|||
}
|
||||
}
|
||||
.onReceive(
|
||||
tab.objectWillChange
|
||||
tab.sidebarObservationPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
// Prompt-time sidebar telemetry can arrive as a short burst
|
||||
// (pwd, branch, PR, shell state). Coalesce that burst so the
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func cmuxInheritedSurfaceConfig(
|
|||
return config
|
||||
}
|
||||
|
||||
struct SidebarStatusEntry {
|
||||
struct SidebarStatusEntry: Equatable {
|
||||
let key: String
|
||||
let value: String
|
||||
let icon: String?
|
||||
|
|
@ -143,7 +143,7 @@ struct SidebarStatusEntry {
|
|||
}
|
||||
}
|
||||
|
||||
struct SidebarMetadataBlock {
|
||||
struct SidebarMetadataBlock: Equatable {
|
||||
let key: String
|
||||
let markdown: String
|
||||
let priority: Int
|
||||
|
|
@ -4896,14 +4896,14 @@ enum SidebarLogLevel: String {
|
|||
case error
|
||||
}
|
||||
|
||||
struct SidebarLogEntry {
|
||||
struct SidebarLogEntry: Equatable {
|
||||
let message: String
|
||||
let level: SidebarLogLevel
|
||||
let source: String?
|
||||
let timestamp: Date
|
||||
}
|
||||
|
||||
struct SidebarProgressState {
|
||||
struct SidebarProgressState: Equatable {
|
||||
let value: Double
|
||||
let label: String?
|
||||
}
|
||||
|
|
@ -4913,6 +4913,14 @@ struct SidebarGitBranchState: Equatable {
|
|||
let isDirty: Bool
|
||||
}
|
||||
|
||||
private struct SidebarPanelObservationState: Equatable {
|
||||
let panelIds: [UUID]
|
||||
|
||||
init(panels: [UUID: any Panel]) {
|
||||
panelIds = panels.keys.sorted { $0.uuidString < $1.uuidString }
|
||||
}
|
||||
}
|
||||
|
||||
enum WorkspaceRemoteConnectionState: String {
|
||||
case disconnected
|
||||
case connecting
|
||||
|
|
@ -5584,6 +5592,47 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
var agentPIDs: [String: pid_t] = [:]
|
||||
private var restoredTerminalScrollbackByPanelId: [UUID: String] = [:]
|
||||
|
||||
private func sidebarObservationSignal<Value: Equatable>(
|
||||
_ publisher: Published<Value>.Publisher
|
||||
) -> AnyPublisher<Void, Never> {
|
||||
publisher
|
||||
.dropFirst()
|
||||
.removeDuplicates()
|
||||
.map { _ in () }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
lazy var sidebarObservationPublisher: AnyPublisher<Void, Never> = {
|
||||
let publishers: [AnyPublisher<Void, Never>] = [
|
||||
sidebarObservationSignal($title),
|
||||
sidebarObservationSignal($isPinned),
|
||||
sidebarObservationSignal($customColor),
|
||||
sidebarObservationSignal($currentDirectory),
|
||||
$panels
|
||||
.map(SidebarPanelObservationState.init)
|
||||
.dropFirst()
|
||||
.removeDuplicates()
|
||||
.map { _ in () }
|
||||
.eraseToAnyPublisher(),
|
||||
sidebarObservationSignal($panelDirectories),
|
||||
sidebarObservationSignal($statusEntries),
|
||||
sidebarObservationSignal($metadataBlocks),
|
||||
sidebarObservationSignal($logEntries),
|
||||
sidebarObservationSignal($progress),
|
||||
sidebarObservationSignal($gitBranch),
|
||||
sidebarObservationSignal($panelGitBranches),
|
||||
sidebarObservationSignal($pullRequest),
|
||||
sidebarObservationSignal($panelPullRequests),
|
||||
sidebarObservationSignal($remoteConfiguration),
|
||||
sidebarObservationSignal($remoteConnectionState),
|
||||
sidebarObservationSignal($remoteConnectionDetail),
|
||||
sidebarObservationSignal($activeRemoteTerminalSessionCount),
|
||||
sidebarObservationSignal($listeningPorts),
|
||||
]
|
||||
|
||||
return Publishers.MergeMany(publishers).eraseToAnyPublisher()
|
||||
}()
|
||||
|
||||
private static func isProxyOnlyRemoteError(_ detail: String) -> Bool {
|
||||
let lowered = detail.lowercased()
|
||||
return lowered.contains("remote proxy")
|
||||
|
|
|
|||
|
|
@ -2191,6 +2191,54 @@ final class WorkspacePanelGitBranchTests: XCTestCase {
|
|||
)
|
||||
}
|
||||
|
||||
func testSidebarObservationPublisherEmitsForFocusedGitBranchChangesOnlyOncePerState() {
|
||||
let workspace = Workspace()
|
||||
guard let panelId = workspace.focusedPanelId else {
|
||||
XCTFail("Expected initial focused panel")
|
||||
return
|
||||
}
|
||||
|
||||
var publishCount = 0
|
||||
let cancellable = workspace.sidebarObservationPublisher.sink {
|
||||
publishCount += 1
|
||||
}
|
||||
defer { cancellable.cancel() }
|
||||
|
||||
workspace.updatePanelGitBranch(panelId: panelId, branch: "main", isDirty: false)
|
||||
let baselinePublishCount = publishCount
|
||||
XCTAssertGreaterThan(
|
||||
baselinePublishCount,
|
||||
0,
|
||||
"Expected focused git branch updates to invalidate sidebar rows"
|
||||
)
|
||||
|
||||
workspace.updatePanelGitBranch(panelId: panelId, branch: "main", isDirty: false)
|
||||
XCTAssertEqual(
|
||||
publishCount,
|
||||
baselinePublishCount,
|
||||
"Expected identical git metadata refreshes to be ignored by sidebar rows"
|
||||
)
|
||||
}
|
||||
|
||||
func testSidebarObservationPublisherIgnoresRemoteHeartbeatOnlyChanges() {
|
||||
let workspace = Workspace()
|
||||
|
||||
var publishCount = 0
|
||||
let cancellable = workspace.sidebarObservationPublisher.sink {
|
||||
publishCount += 1
|
||||
}
|
||||
defer { cancellable.cancel() }
|
||||
|
||||
workspace.remoteHeartbeatCount = 1
|
||||
workspace.remoteLastHeartbeatAt = Date()
|
||||
|
||||
XCTAssertEqual(
|
||||
publishCount,
|
||||
0,
|
||||
"Expected non-visible remote heartbeat updates to avoid invalidating sidebar rows"
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testSidebarPullRequestsTrackFocusedPanelOnly() {
|
||||
let workspace = Workspace()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue