Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
157fa87
Reclaim offscreen terminal renderer GPU memory (IOSurface) non-destru…
lawrence703 Jun 11, 2026
f827c53
Pin GhosttyKit checksum for ghostty 858e257f0 (renderer_realized API)
lawrence703 Jun 11, 2026
a0bcc73
Refresh Swift file length budget for renderer-realization additions
lawrence703 Jun 11, 2026
c6ceb19
Address review: authoritative portal-visibility gate for renderer rel…
lawrence703 Jun 11, 2026
b64af83
Merge remote-tracking branch 'origin/main' into feat-surface-leak-repro
lawrence703 Jun 11, 2026
1851c92
Address review: complete settings integration for renderer reclamation
lawrence703 Jun 11, 2026
731f22a
Address review: wake renderer controller on setting change from any s…
lawrence703 Jun 11, 2026
a071eb0
Address review: validate live Ghostty surface before renderer C calls
lawrence703 Jun 11, 2026
604e1ce
Address review: convert planner test to Swift Testing
lawrence703 Jun 11, 2026
e327286
Address review: DocC on new catalog keys + split planner into its own…
lawrence703 Jun 11, 2026
051e21d
Address review: only advance renderer state on a successful native en…
lawrence703 Jun 11, 2026
6b26d0c
docs: bump ghostty-fork pinned head to d39ba5d84 (enqueue-result API)
lawrence703 Jun 11, 2026
4730951
Pin GhosttyKit checksum for ghostty d39ba5d84 (enqueue-result API)
lawrence703 Jun 11, 2026
5fb0756
Address review: ensure re-show realize is enqueued before presenting …
lawrence703 Jun 11, 2026
66572f6
Address review: measure offscreen idle from the hide moment (P2)
lawrence703 Jun 12, 2026
e454320
Address review: don't reset offscreen-idle clock on repeated hidden u…
lawrence703 Jun 12, 2026
aef6409
Merge remote-tracking branch 'origin/main' into feat-surface-leak-repro
lawrence703 Jun 12, 2026
2d4975d
Merge remote-tracking branch 'origin/main' into feat-surface-leak-repro
lawrence703 Jun 12, 2026
5af7b67
Address review: non-blocking renderer enqueue, drop retry loop (P1)
lawrence703 Jun 12, 2026
6a342e6
Pin GhosttyKit checksum for ghostty 5697db81 (.instant enqueue) + for…
lawrence703 Jun 12, 2026
27cd310
Address review: self-heal a dropped re-show realize on the next runloop
lawrence703 Jun 12, 2026
3b3854c
Merge remote-tracking branch 'origin/main' into feat-surface-leak-repro
lawrencecchen Jun 12, 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
37 changes: 17 additions & 20 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
# Format: max_lines<TAB>relative path
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
33266 CLI/cmux.swift
518 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface.swift
14774 Sources/TerminalController.swift
19985 Sources/Workspace.swift
19275 Sources/ContentView.swift
18116 Sources/AppDelegate.swift
16539 Sources/GhosttyTerminalView.swift
13608 Sources/Panels/BrowserPanel.swift
18117 Sources/AppDelegate.swift
16673 Sources/GhosttyTerminalView.swift
14774 Sources/TerminalController.swift
13606 Sources/Panels/BrowserPanel.swift
12044 cmuxTests/AppDelegateShortcutRoutingTests.swift
10020 Sources/TabManager.swift
9345 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
Expand Down Expand Up @@ -38,17 +37,17 @@
2871 cmuxTests/CMUXOpenCommandTests.swift
2565 Sources/Panels/CmuxWebView.swift
2545 cmuxTests/WorkspaceManualUnreadTests.swift
2527 Sources/TerminalNotificationStore.swift
2544 cmuxTests/CommandPaletteSearchEngineTests.swift
2527 Sources/TerminalNotificationStore.swift
2516 Sources/KeyboardShortcutSettings.swift
2327 cmuxTests/CJKIMEInputTests.swift
2340 Sources/Mobile/MobileHostService.swift
2327 cmuxTests/CJKIMEInputTests.swift
2290 Sources/FileExplorerView.swift
2260 Sources/TerminalWindowPortal.swift
2138 Sources/SessionPersistence.swift
2123 cmuxTests/ShortcutAndCommandPaletteTests.swift
2117 cmuxTests/CmuxConfigTests.swift
1996 Sources/KeyboardShortcutSettingsFileStore.swift
2030 Sources/KeyboardShortcutSettingsFileStore.swift
1949 Sources/Panels/BrowserWebAuthnSupport.swift
1860 cmuxTests/NotificationAndMenuBarTests.swift
1793 Sources/SessionIndexStore.swift
Expand Down Expand Up @@ -94,11 +93,12 @@
924 Sources/DockPanelView.swift
913 cmuxTests/WorkspaceGroupTests.swift
905 Sources/CmuxSSHURLRequest.swift
896 Sources/CommandPalette/CommandPaletteSettingsToggle.swift
878 Sources/WorkspaceContentView.swift
868 Sources/Panels/BrowserScreenshotSnapshotter.swift
866 Sources/CommandPalette/CommandPaletteSettingsToggle.swift
864 Sources/Panels/TerminalPanel.swift
856 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
852 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Workspace/ControlCommandCoordinator+Workspace.swift
846 cmuxTests/AgentSessionAutoResumeSettingsTests.swift
845 cmuxTests/SSHStartupSignalLifecycleTests.swift
842 Sources/Panels/MarkdownWebRenderer.swift
Expand All @@ -112,6 +112,7 @@
757 Packages/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator.swift
756 Sources/Panels/AgentSessionWebRendererCoordinator.swift
752 cmuxUITests/CloseWorkspaceCmdDUITests.swift
750 Sources/TerminalController+ControlWorkspaceContext.swift
739 Sources/App/MenuBarExtraController.swift
738 Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcodeProjectAdapter.swift
716 Sources/TaskManagerSnapshot.swift
Expand All @@ -130,8 +131,8 @@
683 Sources/Panels/CodexAppServerSession.swift
681 Sources/Panels/AgentSessionProcessStore.swift
680 Sources/FileExplorerSearchController.swift
668 cmuxTests/FeedCoordinatorTests.swift
679 cmuxTests/KeyboardShortcutContextTests.swift
668 cmuxTests/FeedCoordinatorTests.swift
654 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/KeyboardShortcutsSection.swift
650 Sources/Panels/MarkdownRemoteImageLoader.swift
649 Sources/CmuxTopSnapshot.swift
Expand All @@ -143,8 +144,9 @@
619 cmuxTests/FinderFileDropRegressionTests.swift
614 cmuxTests/SessionIndexViewTests.swift
613 Sources/PortScanner.swift
611 Sources/TerminalController+ControlPaneContext.swift
603 Sources/SettingsNavigation.swift
599 Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizerPrimaryPolicies.swift
599 Sources/SettingsNavigation.swift
596 cmuxTests/CmuxEventBusTests.swift
594 Sources/SessionIndexModels.swift
594 cmuxTests/PortalTabDragRoutingTests.swift
Expand All @@ -168,31 +170,26 @@
539 CLI/CodexTeamsApprovalBridge.swift
538 CLI/CMUXCLI+Themes.swift
536 cmuxTests/CmuxConfigContextMenuTests.swift
533 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Pane/ControlCommandCoordinator+Pane.swift
531 Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift
531 Sources/App/WorkspaceRuntimeSettings.swift
530 Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttyRuntime.swift
528 cmuxTests/CLINotifyProcessTestSupport.swift
528 cmuxUITests/AutomationSocketUITests.swift
527 CLI/CLISocketPathResolver.swift
520 CLI/CMUXCLI+AmpExtension.swift
520 cmuxTests/MainWindowVisibilityControllerTests.swift
519 Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/Corpus/stress-two-column-cockpit-sidebar.swift
518 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface.swift
518 Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
518 Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/Corpus/stress-git-review-queue-command-deck.swift
516 Sources/CmuxConfigExecutor.swift
514 Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/ExpressionEvaluator.swift
514 cmuxUITests/UpdatePillUITests.swift
518 Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
509 Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizerAdditionalPolicies.swift
507 Sources/TerminalControllerTopSupport.swift
506 Sources/App/MainWindowVisibilityController.swift
505 cmuxUITests/DisplayResolutionRegressionUITests.swift
504 cmuxTests/TerminalNotificationSocketActionTests.swift
502 Sources/CmuxEventPublishing.swift
502 Sources/Settings/ConfigSource.swift
611 Sources/TerminalController+ControlPaneContext.swift
533 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Pane/ControlCommandCoordinator+Pane.swift
852 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Workspace/ControlCommandCoordinator+Workspace.swift
756 Sources/TerminalController+ControlWorkspaceContext.swift
645 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Browser/ControlCommandCoordinator+BrowserAutomationActions.swift
642 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Browser/ControlCommandCoordinator+Browser3.swift
595 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Browser/ControlCommandCoordinator+BrowserAutomation.swift
563 Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Browser/ControlCommandCoordinator+BrowserAutomationFind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@ public struct TerminalCatalogSection: SettingCatalogSection {
userDefaultsKey: "terminal.agentHibernation.maxLiveTerminals"
)

/// Whether off-screen terminals release their GPU renderer memory while
/// idle (rebuilt instantly on re-show). Non-destructive; on by default.
public let rendererRealizationEnabled = DefaultsKey<Bool>(
id: "terminal.rendererRealization.enabled",
defaultValue: true,
userDefaultsKey: "terminal.rendererRealization.enabled"
)

/// Seconds a terminal must stay off-screen before its renderer memory is
/// reclaimed.
public let rendererRealizationIdleSeconds = DefaultsKey<Double>(
id: "terminal.rendererRealization.idleSeconds",
defaultValue: 30,
userDefaultsKey: "terminal.rendererRealization.idleSeconds"
)

/// Most-recently-visible terminals to keep renderer-ready so switching stays
/// instant. Extra off-screen renderers are reclaimed oldest first.
public let rendererRealizationMaxWarmRenderers = DefaultsKey<Int>(
id: "terminal.rendererRealization.maxWarmRenderers",
defaultValue: 12,
userDefaultsKey: "terminal.rendererRealization.maxWarmRenderers"
)

public let showTextBoxOnNewTerminals = DefaultsKey<Bool>(
id: "terminal.showTextBoxOnNewTerminals",
defaultValue: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ extension Array where Element == CuratedSettingEntry {
.init(section: .terminal, id: "agent-hibernation", title: "Agent Hibernation", synonyms: "terminal.agentHibernation.enabled idle hibernate suspend background agents claude code codex opencode live terminals"),
.init(section: .terminal, id: "agent-hibernation-idle", title: "Hibernate After Idle Seconds", synonyms: "terminal.agentHibernation.idleSeconds idle seconds timeout delay hibernate suspend"),
.init(section: .terminal, id: "agent-hibernation-max", title: "Max Live Agent Terminals", synonyms: "terminal.agentHibernation.maxLiveTerminals max live agent terminals limit count hibernate"),
.init(section: .terminal, id: "renderer-realization", title: "Reclaim Offscreen Terminal Memory", synonyms: "terminal.rendererRealization.enabled renderer reclaim offscreen memory iosurface gpu idle warm release background terminals"),
.init(section: .terminal, id: "renderer-realization-idle", title: "Reclaim After Idle Seconds", synonyms: "terminal.rendererRealization.idleSeconds renderer reclaim idle seconds timeout delay offscreen memory"),
.init(section: .terminal, id: "renderer-realization-max", title: "Max Warm Renderers", synonyms: "terminal.rendererRealization.maxWarmRenderers max warm renderers limit count reclaim offscreen gpu"),
Comment on lines +71 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Localize the new settings search titles

These new curated entries feed SettingsSearchIndex.Entry.title, which SettingsSidebarEntryRow renders as the user-facing Settings search result title; adding English literals here means the Japanese Settings search sidebar shows these renderer settings in English even though the actual rows were localized in Localizable.xcstrings. Please use localized strings for the new titles (and add the corresponding catalog entries) so search results match the localized settings UI.

Useful? React with 👍 / 👎.

.init(section: .terminal, id: "resume-commands", title: "Resume Commands", synonyms: "terminal.resumeCommands surface resume command approvals prefixes auto restore prompt manual tmux hibernation"),

// TextBox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public struct TerminalSection: View {
@State private var hibernation: DefaultsValueModel<Bool>
@State private var idleSeconds: DefaultsValueModel<Double>
@State private var maxLive: DefaultsValueModel<Int>
@State private var rendererReclaim: DefaultsValueModel<Bool>
@State private var rendererIdleSeconds: DefaultsValueModel<Double>
@State private var rendererMaxWarm: DefaultsValueModel<Int>

public init(
defaultsStore: UserDefaultsSettingsStore,
Expand All @@ -37,6 +40,9 @@ public struct TerminalSection: View {
_hibernation = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.agentHibernationEnabled))
_idleSeconds = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.agentHibernationIdleSeconds))
_maxLive = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.agentHibernationMaxLiveTerminals))
_rendererReclaim = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.rendererRealizationEnabled))
_rendererIdleSeconds = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.rendererRealizationIdleSeconds))
_rendererMaxWarm = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.rendererRealizationMaxWarmRenderers))
}

public var body: some View {
Expand Down Expand Up @@ -210,6 +216,49 @@ public struct TerminalSection: View {
)
.accessibilityIdentifier("SettingsTerminalAgentHibernationMaxLiveStepper")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.rendererRealization.enabled"),
String(localized: "settings.terminal.rendererRealization", defaultValue: "Reclaim Offscreen Terminal Memory"),
subtitle: rendererReclaim.current
? String(localized: "settings.terminal.rendererRealization.subtitleOn", defaultValue: "Off-screen terminals release their GPU renderer memory while idle and rebuild it instantly when you switch back. The process keeps running.")
: String(localized: "settings.terminal.rendererRealization.subtitleOff", defaultValue: "Every visited terminal keeps its full GPU renderer allocated until you close it, even when off-screen.")
) {
Toggle("", isOn: Binding(get: { rendererReclaim.current }, set: { rendererReclaim.set($0) }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Notify the controller when enabling from Settings

When this toggle is changed in the Settings UI, DefaultsValueModel.set only writes UserDefaults; it does not post RendererRealizationSettings.didChangeNotification, which is the only notification RendererRealizationController.start() observes before creating its timer. In the common case where a user previously disabled terminal.rendererRealization.enabled and then turns it back on here, no timer is started until some other notification path or an app restart, so the setting appears enabled but offscreen renderer reclamation never runs.

Useful? React with 👍 / 👎.

.labelsHidden()
.controlSize(.small)
.accessibilityIdentifier("SettingsTerminalRendererRealizationToggle")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.rendererRealization.idleSeconds"),
String(localized: "settings.terminal.rendererRealization.idleSeconds", defaultValue: "Reclaim After Idle Seconds"),
subtitle: String(localized: "settings.terminal.rendererRealization.idleSeconds.subtitle", defaultValue: "An off-screen terminal must stay off-screen this long before its renderer memory is reclaimed."),
controlWidth: 140
) {
Stepper(
"\(Int(rendererIdleSeconds.current))",
value: Binding(get: { rendererIdleSeconds.current }, set: { rendererIdleSeconds.set($0) }),
in: 5...604_800,
step: 10
)
.accessibilityIdentifier("SettingsTerminalRendererRealizationIdleSecondsStepper")
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.rendererRealization.maxWarmRenderers"),
String(localized: "settings.terminal.rendererRealization.maxWarmRenderers", defaultValue: "Max Warm Renderers"),
subtitle: String(localized: "settings.terminal.rendererRealization.maxWarmRenderers.subtitle", defaultValue: "The most recently visible terminals keep their renderer ready so switching stays instant. Extra off-screen renderers are reclaimed oldest first."),
controlWidth: 120
) {
Stepper(
"\(rendererMaxWarm.current)",
value: Binding(get: { rendererMaxWarm.current }, set: { rendererMaxWarm.set($0) }),
in: 1...256,
step: 1
)
.accessibilityIdentifier("SettingsTerminalRendererRealizationMaxWarmStepper")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ struct SettingsRowAnchorResolutionTests {
"terminal.agentHibernation.enabled",
"terminal.agentHibernation.idleSeconds",
"terminal.agentHibernation.maxLiveTerminals",
"terminal.rendererRealization.enabled",
"terminal.rendererRealization.idleSeconds",
"terminal.rendererRealization.maxWarmRenderers",
"terminal.autoResumeAgentSessions",
"terminal.copyOnSelect",
"terminal.resumeCommands",
Expand Down
119 changes: 119 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -152112,6 +152112,125 @@
}
}
},
"settings.terminal.rendererRealization": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reclaim Offscreen Terminal Memory"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "オフスクリーン端末のメモリを回収"
}
}
}
},
"settings.terminal.rendererRealization.subtitleOn": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Off-screen terminals release their GPU renderer memory while idle and rebuild it instantly when you switch back. The process keeps running."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "オフスクリーンの端末はアイドル中にGPUレンダラーのメモリを解放し、切り替え時に即座に再構築します。プロセスは動作し続けます。"
}
}
}
},
"settings.terminal.rendererRealization.subtitleOff": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Every visited terminal keeps its full GPU renderer allocated until you close it, even when off-screen."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "訪問した端末は、オフスクリーンでも閉じるまでGPUレンダラーをすべて確保し続けます。"
}
}
}
},
"settings.terminal.rendererRealization.idleSeconds": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reclaim After Idle Seconds"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "アイドル秒数経過後に回収"
}
}
}
},
"settings.terminal.rendererRealization.idleSeconds.subtitle": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "An off-screen terminal must stay off-screen this long before its renderer memory is reclaimed."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "オフスクリーンの端末は、レンダラーのメモリが回収されるまでこの時間オフスクリーンのままである必要があります。"
}
}
}
},
"settings.terminal.rendererRealization.maxWarmRenderers": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Max Warm Renderers"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ウォームレンダラーの最大数"
}
}
}
},
"settings.terminal.rendererRealization.maxWarmRenderers.subtitle": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The most recently visible terminals keep their renderer ready so switching stays instant. Extra off-screen renderers are reclaimed oldest first."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "最近表示した端末はレンダラーを準備状態に保ち、切り替えを高速に保ちます。余分なオフスクリーンのレンダラーは古いものから回収されます。"
}
}
}
},
"settings.terminal.agentHibernation.subtitleOff": {
"extractionState": "manual",
"localizations": {
Expand Down
Loading
Loading