Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
582a498
feat: add terminal timestamp gutter
austinywang May 12, 2026
846ea15
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 12, 2026
b154a7a
fix: clamp terminal timestamp scrollbar values
austinywang May 12, 2026
678d830
fix: address terminal timestamp review feedback
austinywang May 12, 2026
9f9946f
fix: align timestamp gutter with live scroll position
austinywang May 12, 2026
e9a6a22
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 12, 2026
685fb40
fix: extract terminal timestamp store
austinywang May 12, 2026
88eb7d4
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 12, 2026
9b16076
fix: avoid timestamp prune hot-path copies
austinywang May 12, 2026
f22d321
fix: isolate terminal timestamp store on main actor
austinywang May 12, 2026
6a00a7b
ci: retrigger terminal timestamp checks
austinywang May 12, 2026
32e4969
fix: bound timestamp prune after scrollback shrink
austinywang May 12, 2026
ec41fc6
test: cover timestamp visible retention
austinywang May 12, 2026
5d8e7f9
fix: preserve marked timestamp rows
austinywang May 12, 2026
56054d3
fix: bound timestamp growth stamping
austinywang May 12, 2026
77b0536
test: cover timestamp viewport retention
austinywang May 12, 2026
6923862
fix: preserve visible timestamp rows while scrolling
austinywang May 12, 2026
cd37d5e
fix: cache terminal timestamp gutter draw style
austinywang May 13, 2026
2255067
fix: reset timestamp rows after scrollback rebase
austinywang May 13, 2026
5ecdcc9
fix: qualify timestamp store bounds assignment
austinywang May 13, 2026
9c3c589
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 13, 2026
781bd8d
fix: bound timestamp prune cursor
austinywang May 13, 2026
ff3745f
fix: preserve keyboard scroll timestamp policy
austinywang May 13, 2026
fe51489
fix: carry keyboard scroll intent across callback
austinywang May 13, 2026
b9e8e28
fix: preserve coalesced keyboard scroll intent
austinywang May 13, 2026
aa2d8fd
fix: limit keyboard scroll intent to viewport motions
austinywang May 13, 2026
d114d2a
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 14, 2026
464b20b
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 17, 2026
9785587
fix: preserve timestamp prune bounds
austinywang May 17, 2026
9b5cd7d
fix: share timestamp scroll intent enqueue path
austinywang May 18, 2026
5fec0ca
Merge branch 'main' of https://github.com/manaflow-ai/cmux into issue…
austinywang May 19, 2026
72d4d93
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 19, 2026
9b980a1
Merge origin/main into issue-3690-terminal-timestamps
austinywang May 19, 2026
c1d98b9
Fix search scrollbar sync after keyboard backscroll
austinywang May 19, 2026
dac1051
Remove obsolete keyboard scroll intent helper
austinywang May 19, 2026
50c6c59
Add terminal timestamp localizations
austinywang May 19, 2026
95c28ce
Merge remote-tracking branch 'origin/main' into issue-3690-terminal-t…
austinywang May 22, 2026
38b5add
merge: sync terminal timestamps with main
austinywang May 22, 2026
9c1a74e
fix: align cli helpers with main
austinywang May 22, 2026
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
4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
E30750000000000000000002 /* WorkspaceTabColorResolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30750000000000000000001 /* WorkspaceTabColorResolution.swift */; };
A5001004 /* GhosttyConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001014 /* GhosttyConfig.swift */; };
A5001005 /* GhosttyTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001015 /* GhosttyTerminalView.swift */; };
D3690A0023690A0023690A02 /* TerminalTimestampStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3690A0013690A0013690A01 /* TerminalTimestampStore.swift */; };
C13519000000000000000001 /* TerminalStartupEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13519000000000000000002 /* TerminalStartupEnvironment.swift */; };
C13519000000000000000005 /* RestorableAgentTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13519000000000000000006 /* RestorableAgentTypes.swift */; };
B3575000000000000000000C /* SessionIndexModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3575000000000000000000B /* SessionIndexModels.swift */; };
Expand Down Expand Up @@ -640,6 +641,7 @@
E30750000000000000000001 /* WorkspaceTabColorResolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceTabColorResolution.swift; sourceTree = "<group>"; };
A5001014 /* GhosttyConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyConfig.swift; sourceTree = "<group>"; };
A5001015 /* GhosttyTerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyTerminalView.swift; sourceTree = "<group>"; };
D3690A0013690A0013690A01 /* TerminalTimestampStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalTimestampStore.swift; sourceTree = "<group>"; };
C13519000000000000000002 /* TerminalStartupEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalStartupEnvironment.swift; sourceTree = "<group>"; };
C13519000000000000000006 /* RestorableAgentTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorableAgentTypes.swift; sourceTree = "<group>"; };
B3575000000000000000000B /* SessionIndexModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIndexModels.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1180,6 +1182,7 @@
E30760000000000000000001 /* TmuxWorkspacePaneOverlayView.swift */,
A5001014 /* GhosttyConfig.swift */,
A5001015 /* GhosttyTerminalView.swift */,
D3690A0013690A0013690A01 /* TerminalTimestampStore.swift */,
C13519000000000000000002 /* TerminalStartupEnvironment.swift */,
C13519000000000000000006 /* RestorableAgentTypes.swift */,
B3575000000000000000000B /* SessionIndexModels.swift */,
Expand Down Expand Up @@ -1815,6 +1818,7 @@
E30760000000000000000002 /* TmuxWorkspacePaneOverlayView.swift in Sources */,
A5001004 /* GhosttyConfig.swift in Sources */,
A5001005 /* GhosttyTerminalView.swift in Sources */,
D3690A0023690A0023690A02 /* TerminalTimestampStore.swift in Sources */,
C13519000000000000000001 /* TerminalStartupEnvironment.swift in Sources */,
C13519000000000000000005 /* RestorableAgentTypes.swift in Sources */,
B3575000000000000000000C /* SessionIndexModels.swift in Sources */,
Expand Down
136 changes: 136 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -105429,6 +105429,142 @@
}
}
},
"settings.terminal.timestamps": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Show Terminal Timestamps"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルのタイムスタンプを表示"
}
}
}
},
"settings.terminal.timestamps.subtitleOff": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Hides the terminal timestamp gutter. Timestamps can also be toggled from the View menu or command palette."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルのタイムスタンプ欄を隠します。タイムスタンプは表示メニューやコマンドパレットからも切り替えられます。"
}
}
}
},
"settings.terminal.timestamps.subtitleOn": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Shows a left-side time gutter aligned with terminal output rows."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナル出力行にそろえた左側の時刻欄を表示します。"
}
}
}
},
"shortcut.toggleTerminalTimestamps.label": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Toggle Terminal Timestamps"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルタイムスタンプを切り替え"
}
}
}
},
"menu.view.toggleTerminalTimestamps": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Toggle Terminal Timestamps"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルタイムスタンプを切り替え"
}
}
}
},
"command.terminalToggleTimestamps.title": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Toggle Terminal Timestamps"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルタイムスタンプを切り替え"
}
}
}
},
"terminal.timestamps.gutter.accessibility": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Terminal timestamps"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルタイムスタンプ"
}
}
}
},
"settings.search.alias.setting.terminal.timestamps": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "terminal.showTimestamps timestamps time gutter output lines long running agent hang debug"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "terminal.showTimestamps タイムスタンプ 時刻 出力 行 長時間 エージェント 停止 デバッグ"
}
}
}
},
"settings.terminal.agentAutoResume": {
"extractionState": "manual",
"localizations": {
Expand Down
37 changes: 37 additions & 0 deletions Sources/App/WorkspaceRuntimeSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,43 @@ enum TerminalScrollBarSettings {
}
}

enum TerminalTimestampsSettings {
static let showTimestampsKey = "terminal.showTimestamps"
static let defaultShowTimestamps = false
static let didChangeNotification = Notification.Name("cmux.terminalTimestampsSettingsDidChange")

static func isVisible(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: showTimestampsKey) == nil {
return defaultShowTimestamps
}
return defaults.bool(forKey: showTimestampsKey)
}

@discardableResult
static func setVisible(
_ visible: Bool,
defaults: UserDefaults = .standard,
notificationCenter: NotificationCenter = .default
) -> Bool {
guard isVisible(defaults: defaults) != visible else { return visible }
defaults.set(visible, forKey: showTimestampsKey)
notifyDidChange(notificationCenter: notificationCenter)
return visible
}

@discardableResult
static func toggle(
defaults: UserDefaults = .standard,
notificationCenter: NotificationCenter = .default
) -> Bool {
setVisible(!isVisible(defaults: defaults), defaults: defaults, notificationCenter: notificationCenter)
}

static func notifyDidChange(notificationCenter: NotificationCenter = .default) {
notificationCenter.post(name: didChangeNotification, object: nil)
}
}

enum AgentSessionAutoResumeSettings {
static let autoResumeAgentSessionsKey = "terminal.autoResumeAgentSessions"
static let defaultAutoResumeAgentSessions = true
Expand Down
11 changes: 11 additions & 0 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11332,6 +11332,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return handled
}

if matchConfiguredShortcut(event: event, action: .toggleTerminalTimestamps) {
let enabled = TerminalTimestampsSettings.toggle()
#if DEBUG
cmuxDebugLog(
"shortcut.action name=toggleTerminalTimestamps enabled=\(enabled ? 1 : 0) " +
"\(debugShortcutRouteSnapshot(event: event))"
)
#endif
return true
}

// Workspace navigation: Cmd+Ctrl+] / Cmd+Ctrl+[
if matchConfiguredShortcut(event: event, action: .nextSidebarTab) {
#if DEBUG
Expand Down
1 change: 1 addition & 0 deletions Sources/CmuxSettingsJSONPathSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ extension CmuxSettingsFileStore {
"app.renameSelectsExistingName",
"app.commandPaletteSearchesAllSurfaces",
"terminal.showScrollBar",
"terminal.showTimestamps",
"terminal.autoResumeAgentSessions",
"notifications.dockBadge",
"notifications.showInMenuBar",
Expand Down
2 changes: 2 additions & 0 deletions Sources/ContentView+RightSidebarCommandPalette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ extension ContentView {
return .hideFind
case "palette.terminalUseSelectionForFind":
return .useSelectionForFind
case "palette.toggleTerminalTimestamps":
return .toggleTerminalTimestamps
case "palette.toggleSplitZoom":
return .toggleSplitZoom
case "palette.equalizeSplits":
Expand Down
15 changes: 15 additions & 0 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6008,6 +6008,8 @@ struct ContentView: View {
return "⌥⌘⇧F"
case "palette.terminalUseSelectionForFind":
return "⌘E"
case "palette.toggleTerminalTimestamps":
return "⌥⌘⇧E"
case "palette.toggleFullScreen":
return "\u{2303}\u{2318}F"
default:
Expand Down Expand Up @@ -6899,6 +6901,16 @@ struct ContentView: View {
when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) }
)
)
contributions.append(
CommandPaletteCommandContribution(
commandId: "palette.toggleTerminalTimestamps",
title: constant(String(localized: "command.terminalToggleTimestamps.title", defaultValue: "Toggle Terminal Timestamps")),
subtitle: terminalPanelSubtitle,
shortcutHint: "⌥⌘⇧E",
keywords: ["terminal", "timestamps", "time", "output"],
when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) }
)
)
contributions.append(
CommandPaletteCommandContribution(
commandId: "palette.terminalSplitRight",
Expand Down Expand Up @@ -7466,6 +7478,9 @@ struct ContentView: View {
registry.register(commandId: "palette.terminalUseSelectionForFind") {
tabManager.searchSelection()
}
registry.register(commandId: "palette.toggleTerminalTimestamps") {
_ = TerminalTimestampsSettings.toggle()
}
registry.register(commandId: "palette.terminalSplitRight") {
if !executeConfiguredAction(id: CmuxSurfaceTabBarBuiltInAction.splitRight.configID) {
tabManager.createSplit(direction: .right)
Expand Down
Loading
Loading