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
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public struct TerminalSection: View {
configurationReview: .json("terminal.showScrollBar"),
String(localized: "settings.terminal.scrollBar", defaultValue: "Show Terminal Scroll Bar"),
subtitle: scrollBar.current
? String(localized: "settings.terminal.scrollBar.subtitleOn", defaultValue: "Shows the right-edge terminal scroll bar in shell scrollback. cmux hides it automatically for alternate-screen style TUI surfaces and you can also disable it per workspace.")
? String(localized: "settings.terminal.scrollBar.subtitleOn", defaultValue: "Shows the right-edge terminal scroll bar in shell scrollback. cmux hides it automatically for alternate-screen style TUI surfaces.")
: String(localized: "settings.terminal.scrollBar.subtitleOff", defaultValue: "Hides the right-edge terminal scroll bar everywhere. Changes apply immediately and persist across relaunches.")
) {
Toggle("", isOn: Binding(get: { scrollBar.current }, set: { scrollBar.set($0) }))
Expand Down
38 changes: 2 additions & 36 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -142512,40 +142512,6 @@
}
}
},
"contextMenu.workspaceSettings": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Workspace Settings"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ワークスペース設定"
}
}
}
},
"contextMenu.workspaceSettings.hideTerminalScrollBar": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Hide Terminal Scroll Bar"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルのスクロールバーを隠す"
}
}
}
},
"settings.section.terminal": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -142603,13 +142569,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Shows the right-edge terminal scroll bar in shell scrollback. cmux hides it automatically for alternate-screen style TUI surfaces and you can also disable it per workspace."
"value": "Shows the right-edge terminal scroll bar in shell scrollback. cmux hides it automatically for alternate-screen style TUI surfaces."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "シェルのスクロールバックでは右端のターミナルスクロールバーを表示します。cmux は代替画面系の TUI では自動的に隠し、ワークスペースごとにも無効化できます。"
"value": "シェルのスクロールバックでは右端のターミナルスクロールバーを表示します。cmux は代替画面系の TUI では自動的に隠します。"
}
}
}
Expand Down
58 changes: 0 additions & 58 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10234,7 +10234,6 @@ struct VerticalTabsSidebar: View {
// row sitting behind the open menu. See `SidebarShortcutHintFreezePolicy`.
@State private var frozenShortcutHintsTabId: UUID?
@State private var frozenShortcutHintsValue: Bool = false
@State private var terminalScrollBarVisibilityGeneration: UInt64 = 0
@State private var laidOutWorkspaceRowIds: Set<UUID> = []
@State private var pendingSelectedWorkspaceScrollId: UUID?
@State private var collapsedExtensionSidebarSectionIds: Set<String> = []
Expand Down Expand Up @@ -10422,7 +10421,6 @@ struct VerticalTabsSidebar: View {
let selectedRemoteContextMenuWorkspaceIds: [UUID]
let allSelectedRemoteContextMenuTargetsConnecting: Bool
let allSelectedRemoteContextMenuTargetsDisconnected: Bool
let workspaceTerminalScrollBarHiddenById: [UUID: Bool]
let workspaceGroups: [WorkspaceGroup]
let workspaceGroupById: [UUID: WorkspaceGroup]
let workspaceGroupMenuSnapshot: WorkspaceGroupMenuSnapshot
Expand All @@ -10431,7 +10429,6 @@ struct VerticalTabsSidebar: View {
}

var body: some View {
let _ = terminalScrollBarVisibilityGeneration
let tabs = tabManager.tabs
let workspaceCount = tabs.count
let canCloseWorkspace = workspaceCount > 1
Expand All @@ -10445,9 +10442,6 @@ struct VerticalTabsSidebar: View {
let selectedContextTargetIds = orderedSelectedTabs.map(\.id)
let selectedRemoteContextMenuTargets = orderedSelectedTabs.filter { $0.isRemoteWorkspace }
let selectedRemoteContextMenuWorkspaceIds = selectedRemoteContextMenuTargets.map(\.id)
let workspaceTerminalScrollBarHiddenById = Dictionary(
uniqueKeysWithValues: tabs.map { ($0.id, $0.terminalScrollBarHidden) }
)
let allSelectedRemoteContextMenuTargetsConnecting = !selectedRemoteContextMenuTargets.isEmpty &&
selectedRemoteContextMenuTargets.allSatisfy {
$0.remoteConnectionState == .connecting || $0.remoteConnectionState == .reconnecting
Expand Down Expand Up @@ -10480,7 +10474,6 @@ struct VerticalTabsSidebar: View {
selectedRemoteContextMenuWorkspaceIds: selectedRemoteContextMenuWorkspaceIds,
allSelectedRemoteContextMenuTargetsConnecting: allSelectedRemoteContextMenuTargetsConnecting,
allSelectedRemoteContextMenuTargetsDisconnected: allSelectedRemoteContextMenuTargetsDisconnected,
workspaceTerminalScrollBarHiddenById: workspaceTerminalScrollBarHiddenById,
workspaceGroups: workspaceGroups,
workspaceGroupById: workspaceGroupById,
workspaceGroupMenuSnapshot: workspaceGroupMenuSnapshot
Expand Down Expand Up @@ -10576,19 +10569,6 @@ struct VerticalTabsSidebar: View {
!tabIds.contains(frozenTabId) else { return }
frozenShortcutHintsTabId = nil
}
.onReceive(
NotificationCenter.default.publisher(for: Workspace.terminalScrollBarHiddenDidChangeNotification)
.receive(on: RunLoop.main)
) { notification in
guard let workspace = notification.object as? Workspace,
tabManager.tabs.contains(where: { $0 === workspace }) else {
return
}

// Workspace scrollbar visibility changes do not publish on TabManager.tabs,
// so bump a local generation to refresh the precomputed context-menu state.
terminalScrollBarVisibilityGeneration &+= 1
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}

Expand Down Expand Up @@ -11933,10 +11913,6 @@ struct VerticalTabsSidebar: View {
let allRemoteContextMenuTargetsDisconnected = usesSelectedContextMenuTargets
? renderContext.allSelectedRemoteContextMenuTargetsDisconnected
: (tab.isRemoteWorkspace && tab.remoteConnectionState == .disconnected)
let allContextMenuWorkspacesHideTerminalScrollBar = !contextMenuWorkspaceIds.isEmpty &&
contextMenuWorkspaceIds.allSatisfy { workspaceId in
renderContext.workspaceTerminalScrollBarHiddenById[workspaceId] == true
}
let contextMenuPinTarget = WorkspaceActionDispatcher.Target(
workspaceIds: contextMenuWorkspaceIds,
anchorWorkspaceId: tab.id
Expand Down Expand Up @@ -12037,7 +12013,6 @@ struct VerticalTabsSidebar: View {
remoteContextMenuWorkspaceIds: remoteContextMenuWorkspaceIds,
allRemoteContextMenuTargetsConnecting: allRemoteContextMenuTargetsConnecting,
allRemoteContextMenuTargetsDisconnected: allRemoteContextMenuTargetsDisconnected,
allContextMenuWorkspacesHideTerminalScrollBar: allContextMenuWorkspacesHideTerminalScrollBar,
contextMenuPinState: contextMenuPinState,
workspaceGroupMenuSnapshot: renderContext.workspaceGroupMenuSnapshot,
settings: renderContext.tabItemSettings,
Expand Down Expand Up @@ -14502,7 +14477,6 @@ struct TabItemView: View, Equatable {
lhs.remoteContextMenuWorkspaceIds == rhs.remoteContextMenuWorkspaceIds &&
lhs.allRemoteContextMenuTargetsConnecting == rhs.allRemoteContextMenuTargetsConnecting &&
lhs.allRemoteContextMenuTargetsDisconnected == rhs.allRemoteContextMenuTargetsDisconnected &&
lhs.allContextMenuWorkspacesHideTerminalScrollBar == rhs.allContextMenuWorkspacesHideTerminalScrollBar &&
lhs.contextMenuPinState == rhs.contextMenuPinState &&
lhs.workspaceGroupMenuSnapshot == rhs.workspaceGroupMenuSnapshot &&
lhs.isBeingDragged == rhs.isBeingDragged &&
Expand Down Expand Up @@ -14548,7 +14522,6 @@ struct TabItemView: View, Equatable {
let remoteContextMenuWorkspaceIds: [UUID]
let allRemoteContextMenuTargetsConnecting: Bool
let allRemoteContextMenuTargetsDisconnected: Bool
let allContextMenuWorkspacesHideTerminalScrollBar: Bool
let contextMenuPinState: WorkspaceActionDispatcher.PinState?
let workspaceGroupMenuSnapshot: WorkspaceGroupMenuSnapshot
let settings: SidebarTabItemSettingsSnapshot
Expand Down Expand Up @@ -15446,20 +15419,6 @@ struct TabItemView: View, Equatable {
.disabled(allRemoteContextMenuTargetsDisconnected)
}

Menu(String(localized: "contextMenu.workspaceSettings", defaultValue: "Workspace Settings")) {
Button {
toggleWorkspaceTerminalScrollBarHidden(targetIds: targetIds)
} label: {
Label {
Text(String(localized: "contextMenu.workspaceSettings.hideTerminalScrollBar", defaultValue: "Hide Terminal Scroll Bar"))
} icon: {
if allContextMenuWorkspacesHideTerminalScrollBar {
Image(systemName: "checkmark")
}
}
}
}

Menu(String(localized: "contextMenu.workspaceColor", defaultValue: "Workspace Color")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop honoring stale per-workspace scrollbar flags

When the Workspace Settings menu is removed, users who previously hid the scrollbar for a workspace can no longer unhide it: Workspace still persists/restores terminalScrollBarHidden, and GhosttyTerminalView.terminalScrollBarAllowedBySettings() still returns false when that flag is true, regardless of the global Settings toggle. In that state, turning “Show Terminal Scroll Bar” back on in Settings does not restore the scrollbar for affected/restored workspaces, so this removal needs either a migration/reset path or the per-workspace flag should stop being applied.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the follow-up commits: the sidebar context menu no longer exposes the per-workspace control, terminal rendering no longer reads the per-workspace flag, and session snapshots no longer write or restore that flag. Existing persisted hidden flags are ignored so the global Settings toggle controls the scrollbar again.

— Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified again on the rebased head a79b543: the removed context-menu path is gone, GhosttyTerminalView no longer reads Workspace.terminalScrollBarHidden, Workspace session snapshots no longer write or restore terminalScrollBarHidden, and the autosave fingerprint no longer includes it. Existing persisted hidden flags are ignored, so the global Settings toggle controls scrollbar visibility.

— Claude Code

if tab.customColor != nil {
Button {
Expand Down Expand Up @@ -16262,23 +16221,6 @@ struct TabItemView: View, Equatable {
tabManager.applyWorkspaceColor(hex, toWorkspaceIds: targetIds)
}

private func toggleWorkspaceTerminalScrollBarHidden(targetIds: [UUID]) {
let targetIdSet = Set(targetIds)
var liveTargetCount = 0
var hiddenTargetCount = 0
for workspace in tabManager.tabs where targetIdSet.contains(workspace.id) {
liveTargetCount += 1
if workspace.terminalScrollBarHidden {
hiddenTargetCount += 1
}
}
let currentlyHidden = !targetIdSet.isEmpty &&
liveTargetCount == targetIdSet.count &&
hiddenTargetCount == liveTargetCount
let hideScrollBar = !currentlyHidden
tabManager.setWorkspaceTerminalScrollBarHidden(hidden: hideScrollBar, forWorkspaceIds: targetIds)
}

private func promptCustomColor(targetIds: [UUID]) {
let alert = NSAlert()
alert.messageText = String(localized: "alert.customColor.title", defaultValue: "Custom Workspace Color")
Expand Down
48 changes: 0 additions & 48 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11200,11 +11200,8 @@ final class GhosttySurfaceScrollView: NSView {
private var activeImageTransferOperation: TerminalImageTransferOperation?
private var activeImageTransferCancelHandler: (() -> Void)?
private var lastSearchOverlayStateID: ObjectIdentifier?
private weak var cachedOwningWorkspace: Workspace?
private weak var observedWorkspaceTerminalScrollBar: Workspace?
private var searchOverlayMutationGeneration: UInt64 = 0
private var observers: [NSObjectProtocol] = []
private var workspaceTerminalScrollBarObserver: NSObjectProtocol?
private var windowObservers: [NSObjectProtocol] = []
private var scrollbarTrackingArea: NSTrackingArea?
private var isLiveScrolling = false
Expand Down Expand Up @@ -11752,9 +11749,6 @@ final class GhosttySurfaceScrollView: NSView {
)
#endif
observers.forEach { NotificationCenter.default.removeObserver($0) }
if let workspaceTerminalScrollBarObserver {
NotificationCenter.default.removeObserver(workspaceTerminalScrollBarObserver)
}
windowObservers.forEach { NotificationCenter.default.removeObserver($0) }
deferredSearchOverlayMutationWorkItem?.cancel()
imageTransferIndicatorShowWorkItem?.cancel()
Expand Down Expand Up @@ -12077,9 +12071,6 @@ final class GhosttySurfaceScrollView: NSView {

func attachSurface(_ terminalSurface: TerminalSurface) {
surfaceView.attachSurface(terminalSurface)
let workspace = terminalSurface.owningWorkspace()
cachedOwningWorkspace = workspace
updateWorkspaceTerminalScrollBarObserver(workspace)
// Preserve the bootstrap 800x600 surface until portal reattach churn
// has produced a real host size instead of a transient 1x1 placeholder.
guard bounds.width > 1, bounds.height > 1 else { return }
Expand Down Expand Up @@ -14229,31 +14220,6 @@ final class GhosttySurfaceScrollView: NSView {
_ = synchronizeGeometryAndContent()
}

private func updateWorkspaceTerminalScrollBarObserver(_ workspace: Workspace?) {
if let observedWorkspaceTerminalScrollBar,
observedWorkspaceTerminalScrollBar === workspace,
workspaceTerminalScrollBarObserver != nil {
return
}

if let workspaceTerminalScrollBarObserver {
NotificationCenter.default.removeObserver(workspaceTerminalScrollBarObserver)
self.workspaceTerminalScrollBarObserver = nil
}

observedWorkspaceTerminalScrollBar = workspace

guard let workspace else { return }

workspaceTerminalScrollBarObserver = NotificationCenter.default.addObserver(
forName: Workspace.terminalScrollBarHiddenDidChangeNotification,
object: workspace,
queue: .main
) { [weak self] _ in
self?.handleTerminalScrollBarPreferenceChange()
}
}

private func documentHeight() -> CGFloat {
let contentHeight = scrollView.contentSize.height
let cellHeight = surfaceView.cellSize.height
Expand All @@ -14265,23 +14231,9 @@ final class GhosttySurfaceScrollView: NSView {
return contentHeight
}

private func owningWorkspace() -> Workspace? {
let workspaceId = surfaceView.terminalSurface?.tabId
if let cachedOwningWorkspace,
cachedOwningWorkspace.id == workspaceId {
updateWorkspaceTerminalScrollBarObserver(cachedOwningWorkspace)
return cachedOwningWorkspace
}
let workspace = surfaceView.terminalSurface?.owningWorkspace()
cachedOwningWorkspace = workspace
updateWorkspaceTerminalScrollBarObserver(workspace)
return workspace
}

private func terminalScrollBarAllowedBySettings() -> Bool {
guard GhosttyApp.shared.scrollbarVisibility() != .never else { return false }
guard TerminalScrollBarSettings.isVisible() else { return false }
guard owningWorkspace()?.terminalScrollBarHidden != true else { return false }
return true
}

Expand Down
1 change: 0 additions & 1 deletion Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11263,7 +11263,6 @@ extension TabManager {
hasher.combine(workspace.customDescription ?? "")
hasher.combine(workspace.customColor ?? "")
hasher.combine(workspace.isPinned)
hasher.combine(workspace.terminalScrollBarHidden)
hasher.combine(workspace.panels.count)
hasher.combine(workspace.statusEntries.count)
hasher.combine(workspace.metadataBlocks.count)
Expand Down
5 changes: 2 additions & 3 deletions Sources/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,6 @@ extension Workspace {
isManuallyUnread: isWorkspaceManuallyUnread,
hasUnreadIndicator: hasWorkspaceUnreadIndicator,
notifications: workspaceNotificationSnapshots.isEmpty ? nil : workspaceNotificationSnapshots,
terminalScrollBarHidden: terminalScrollBarHidden ? true : nil,
currentDirectory: currentDirectory,
focusedPanelId: focusedPanelId,
layout: layout,
Expand Down Expand Up @@ -322,7 +321,6 @@ extension Workspace {
setCustomColor(snapshot.customColor)
isPinned = snapshot.isPinned
groupId = snapshot.groupId
setTerminalScrollBarHidden(snapshot.terminalScrollBarHidden ?? false)

// Status entries and agent PIDs are ephemeral runtime state tied to running
// processes (e.g. claude_code "Running"). Don't restore them across app
Expand Down Expand Up @@ -10270,6 +10268,8 @@ final class Workspace: Identifiable, ObservableObject {
/// The group entity itself lives in `TabManager.workspaceGroups`.
@Published var groupId: UUID?
@Published var customColor: String? // hex string, e.g. "#C0392B"
// Legacy in-memory state for old helpers/tests. Product UI, rendering, and
// session persistence no longer honor per-workspace scrollbar overrides.
@Published private(set) var terminalScrollBarHidden: Bool = false
@Published var currentDirectory: String {
didSet {
Expand Down Expand Up @@ -10524,7 +10524,6 @@ final class Workspace: Identifiable, ObservableObject {
sidebarObservationSignal($customDescription),
sidebarObservationSignal($isPinned),
sidebarObservationSignal($customColor),
sidebarObservationSignal($terminalScrollBarHidden),
sidebarObservationSignal($latestConversationMessage),
sidebarObservationSignal($latestSubmittedMessage),
sidebarObservationSignal($latestSubmittedAt),
Expand Down
2 changes: 1 addition & 1 deletion Sources/cmuxApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6994,7 +6994,7 @@ struct SettingsView: View {
configurationReview: .json("terminal.showScrollBar"),
String(localized: "settings.terminal.scrollBar", defaultValue: "Show Terminal Scroll Bar"),
subtitle: showTerminalScrollBar
? String(localized: "settings.terminal.scrollBar.subtitleOn", defaultValue: "Shows the right-edge terminal scroll bar in shell scrollback. cmux hides it automatically for alternate-screen style TUI surfaces and you can also disable it per workspace.")
? String(localized: "settings.terminal.scrollBar.subtitleOn", defaultValue: "Shows the right-edge terminal scroll bar in shell scrollback. cmux hides it automatically for alternate-screen style TUI surfaces.")
: String(localized: "settings.terminal.scrollBar.subtitleOff", defaultValue: "Hides the right-edge terminal scroll bar everywhere. Changes apply immediately and persist across relaunches.")
) {
Toggle("", isOn: showTerminalScrollBarBinding)
Expand Down
Loading