native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #121 from onevcat/fix/font-size-worktree-switch-stability

Fix font size stability across worktree switches

authored by

Wei Wang and committed by
GitHub
be93db6c d0bac0b1

+54 -204
-24
supacode/App/AppShortcuts.swift
··· 111 111 static let selectWorktree7 = "select_worktree_7" 112 112 static let selectWorktree8 = "select_worktree_8" 113 113 static let selectWorktree9 = "select_worktree_9" 114 - static let selectWorktree0 = "select_worktree_0" 115 114 static let selectTerminalTab1 = "select_terminal_tab_1" 116 115 static let selectTerminalTab2 = "select_terminal_tab_2" 117 116 static let selectTerminalTab3 = "select_terminal_tab_3" ··· 121 120 static let selectTerminalTab7 = "select_terminal_tab_7" 122 121 static let selectTerminalTab8 = "select_terminal_tab_8" 123 122 static let selectTerminalTab9 = "select_terminal_tab_9" 124 - static let selectTerminalTab0 = "select_terminal_tab_0" 125 123 static let renameBranch = "rename_branch" 126 124 static let selectAllCanvasCards = "select_all_canvas_cards" 127 125 static let selectPreviousTerminalTab = "select_previous_terminal_tab" ··· 192 190 static let selectWorktree7 = AppShortcut(key: "7", modifiers: [.control]) 193 191 static let selectWorktree8 = AppShortcut(key: "8", modifiers: [.control]) 194 192 static let selectWorktree9 = AppShortcut(key: "9", modifiers: [.control]) 195 - static let selectWorktree0 = AppShortcut(key: "0", modifiers: [.control]) 196 193 static let selectTerminalTab1 = AppShortcut(key: "1", modifiers: [.command]) 197 194 static let selectTerminalTab2 = AppShortcut(key: "2", modifiers: [.command]) 198 195 static let selectTerminalTab3 = AppShortcut(key: "3", modifiers: [.command]) ··· 202 199 static let selectTerminalTab7 = AppShortcut(key: "7", modifiers: [.command]) 203 200 static let selectTerminalTab8 = AppShortcut(key: "8", modifiers: [.command]) 204 201 static let selectTerminalTab9 = AppShortcut(key: "9", modifiers: [.command]) 205 - static let selectTerminalTab0 = AppShortcut(key: "0", modifiers: [.command]) 206 202 static let selectPreviousTerminalTab = AppShortcut(key: "[", modifiers: [.command, .shift]) 207 203 static let selectNextTerminalTab = AppShortcut(key: "]", modifiers: [.command, .shift]) 208 204 static let selectPreviousTerminalPane = AppShortcut(key: "[", modifiers: [.command]) ··· 231 227 selectWorktree7, 232 228 selectWorktree8, 233 229 selectWorktree9, 234 - selectWorktree0, 235 230 ] 236 231 237 232 static let worktreeSelectionCommandIDs: [String] = [ ··· 244 239 CommandID.selectWorktree7, 245 240 CommandID.selectWorktree8, 246 241 CommandID.selectWorktree9, 247 - CommandID.selectWorktree0, 248 242 ] 249 243 250 244 static let terminalTabSelection: [AppShortcut] = [ ··· 257 251 selectTerminalTab7, 258 252 selectTerminalTab8, 259 253 selectTerminalTab9, 260 - selectTerminalTab0, 261 254 ] 262 255 263 256 static let terminalTabSelectionCommandIDs: [String] = [ ··· 270 263 CommandID.selectTerminalTab7, 271 264 CommandID.selectTerminalTab8, 272 265 CommandID.selectTerminalTab9, 273 - CommandID.selectTerminalTab0, 274 266 ] 275 267 276 268 private static let reservedCustomCommandBindings: [ReservedCustomCommandBinding] = [ ··· 291 283 .init(actionTitle: "Select Terminal Tab 7", shortcut: selectTerminalTab7), 292 284 .init(actionTitle: "Select Terminal Tab 8", shortcut: selectTerminalTab8), 293 285 .init(actionTitle: "Select Terminal Tab 9", shortcut: selectTerminalTab9), 294 - .init(actionTitle: "Select Terminal Tab 0", shortcut: selectTerminalTab0), 295 286 .init(actionTitle: "Select Previous Tab", shortcut: selectPreviousTerminalTab), 296 287 .init(actionTitle: "Select Next Tab", shortcut: selectNextTerminalTab), 297 288 .init(actionTitle: "Select Previous Pane", shortcut: selectPreviousTerminalPane), ··· 454 445 shortcut: selectWorktree9 455 446 ), 456 447 .init( 457 - id: CommandID.selectWorktree0, 458 - title: "Select Worktree 0", 459 - scope: .configurableAppAction, 460 - shortcut: selectWorktree0 461 - ), 462 - .init( 463 448 id: CommandID.selectTerminalTab1, 464 449 title: "Select Terminal Tab 1", 465 450 scope: .configurableAppAction, ··· 512 497 title: "Select Terminal Tab 9", 513 498 scope: .configurableAppAction, 514 499 shortcut: selectTerminalTab9 515 - ), 516 - .init( 517 - id: CommandID.selectTerminalTab0, 518 - title: "Select Terminal Tab 0", 519 - scope: .configurableAppAction, 520 - shortcut: selectTerminalTab0 521 500 ), 522 501 .init( 523 502 id: CommandID.selectPreviousTerminalTab, ··· 674 653 (CommandID.selectTerminalTab7, "goto_tab:7"), 675 654 (CommandID.selectTerminalTab8, "goto_tab:8"), 676 655 (CommandID.selectTerminalTab9, "goto_tab:9"), 677 - (CommandID.selectTerminalTab0, "goto_tab:10"), 678 656 (CommandID.selectPreviousTerminalTab, "previous_tab"), 679 657 (CommandID.selectNextTerminalTab, "next_tab"), 680 658 (CommandID.selectPreviousTerminalPane, "goto_split:previous"), ··· 745 723 selectWorktree7, 746 724 selectWorktree8, 747 725 selectWorktree9, 748 - selectWorktree0, 749 726 selectTerminalTab1, 750 727 selectTerminalTab2, 751 728 selectTerminalTab3, ··· 755 732 selectTerminalTab7, 756 733 selectTerminalTab8, 757 734 selectTerminalTab9, 758 - selectTerminalTab0, 759 735 selectPreviousTerminalTab, 760 736 selectNextTerminalTab, 761 737 selectPreviousTerminalPane,
+7 -6
supacode/Features/Repositories/Views/WorktreeDetailView.swift
··· 283 283 } 284 284 285 285 func fontSizeAction(_ bindingAction: String) -> (() -> Void)? { 286 - if bindingAction == "reset_font_size" { 287 - guard hasActiveWorktree else { return nil } 286 + if repositories.isShowingCanvas { 288 287 return { 289 - terminalManager.resetFontSizeAcrossStates() 288 + guard let worktreeID = terminalManager.canvasFocusedWorktreeID, 289 + let state = terminalManager.stateIfExists(for: worktreeID) 290 + else { return } 291 + _ = state.performBindingActionOnFocusedSurface(bindingAction) 292 + terminalManager.syncPreferredFontSize(from: worktreeID) 290 293 } 291 - } 292 - if let action = canvasAction({ $0.performBindingActionOnFocusedSurface(bindingAction) }) { 293 - return action 294 294 } 295 295 guard hasActiveWorktree, let selectedWorktree = repositories.selectedTerminalWorktree else { return nil } 296 296 return { 297 297 guard let state = terminalManager.stateIfExists(for: selectedWorktree.id) else { return } 298 298 _ = state.performBindingActionOnFocusedSurface(bindingAction) 299 + terminalManager.syncPreferredFontSize(from: selectedWorktree.id) 299 300 } 300 301 } 301 302
+1 -5
supacode/Features/Settings/Views/ShortcutsSettingsView.swift
··· 388 388 return 9 389 389 case AppShortcuts.CommandID.selectTerminalTab9: 390 390 return 10 391 - case AppShortcuts.CommandID.selectTerminalTab0: 392 - return 11 393 391 default: 394 392 return nil 395 393 } ··· 917 915 AppShortcuts.CommandID.selectWorktree6, 918 916 AppShortcuts.CommandID.selectWorktree7, 919 917 AppShortcuts.CommandID.selectWorktree8, 920 - AppShortcuts.CommandID.selectWorktree9, 921 - AppShortcuts.CommandID.selectWorktree0: 918 + AppShortcuts.CommandID.selectWorktree9: 922 919 return .navigation 923 920 924 921 case AppShortcuts.CommandID.runScript, ··· 938 935 AppShortcuts.CommandID.selectTerminalTab7, 939 936 AppShortcuts.CommandID.selectTerminalTab8, 940 937 AppShortcuts.CommandID.selectTerminalTab9, 941 - AppShortcuts.CommandID.selectTerminalTab0, 942 938 AppShortcuts.CommandID.selectPreviousTerminalTab, 943 939 AppShortcuts.CommandID.selectNextTerminalTab, 944 940 AppShortcuts.CommandID.selectPreviousTerminalPane,
+7 -17
supacode/Features/Terminal/BusinessLogic/WorktreeTerminalManager.swift
··· 217 217 state.onSetupScriptConsumed = { [weak self] in 218 218 self?.emit(.setupScriptConsumed(worktreeID: worktree.id)) 219 219 } 220 - state.onFontSizeChanged = { [weak self] fontSize in 221 - self?.applyFontSize(fontSize) 220 + state.onFontSizeAdjusted = { [weak self] in 221 + self?.syncPreferredFontSize(from: worktree.id) 222 222 } 223 223 states[worktree.id] = state 224 224 terminalLogger.info("Created terminal state for worktree \(worktree.id)") ··· 347 347 runtime.backgroundOpacity() 348 348 } 349 349 350 - func resetFontSizeAcrossStates() { 351 - let shouldEmit = preferredFontSize != nil 352 - preferredFontSize = nil 353 - for state in states.values { 354 - state.setDefaultFontSize(nil) 355 - _ = state.performBindingActionOnFocusedSurface("reset_font_size") 356 - } 357 - if shouldEmit { 358 - emit(.fontSizeChanged(nil)) 359 - } 360 - } 361 - 362 - private func applyFontSize(_ fontSize: Float32?) { 350 + func syncPreferredFontSize(from worktreeID: Worktree.ID) { 351 + guard let state = states[worktreeID] else { return } 352 + let fontSize = state.focusedFontSize() 363 353 let normalized = normalizedFontSize(fontSize) 364 354 guard preferredFontSize != normalized else { return } 365 355 preferredFontSize = normalized 366 - for state in states.values { 367 - state.setDefaultFontSize(normalized) 356 + for worktreeState in states.values { 357 + worktreeState.setDefaultFontSize(normalized) 368 358 } 369 359 emit(.fontSizeChanged(normalized)) 370 360 }
+16 -32
supacode/Features/Terminal/Models/WorktreeTerminalState.swift
··· 27 27 private var runScriptTabId: TerminalTabID? 28 28 private var pendingSetupScript: Bool 29 29 private var defaultFontSize: Float32? 30 - private var hasInitializedCellSizeSurfaceIDs: Set<UUID> = [] 31 30 private var isEnsuringInitialTab = false 32 31 private var lastReportedTaskStatus: WorktreeTaskStatus? 33 32 private var lastEmittedFocusSurfaceId: UUID? ··· 56 55 var onRunScriptStatusChanged: ((Bool) -> Void)? 57 56 var onCommandPaletteToggle: (() -> Void)? 58 57 var onSetupScriptConsumed: (() -> Void)? 59 - var onFontSizeChanged: ((Float32?) -> Void)? 58 + var onFontSizeAdjusted: (() -> Void)? 60 59 61 60 init( 62 61 runtime: GhosttyRuntime, ··· 118 117 defaultFontSize = fontSize 119 118 } 120 119 120 + func focusedFontSize() -> Float32? { 121 + guard let surfaceId = currentFocusedSurfaceId() else { return nil } 122 + return inheritedSurfaceConfig(fromSurfaceId: surfaceId, context: GHOSTTY_SURFACE_CONTEXT_TAB).fontSize 123 + } 124 + 121 125 func ensureInitialTab(focusing: Bool) { 122 126 guard tabManager.tabs.isEmpty else { return } 123 127 guard !isEnsuringInitialTab else { return } ··· 467 471 } catch { 468 472 newSurface.closeSurface() 469 473 surfaces.removeValue(forKey: newSurface.id) 470 - hasInitializedCellSizeSurfaceIDs.remove(newSurface.id) 474 + 471 475 return false 472 476 } 473 477 ··· 561 565 surface.closeSurface() 562 566 } 563 567 surfaces.removeAll() 564 - hasInitializedCellSizeSurfaceIDs.removeAll() 565 568 trees.removeAll() 566 569 focusedSurfaceIdByTab.removeAll() 567 570 tabIsRunningById.removeAll() ··· 824 827 fontSize: resolvedFontSize, 825 828 context: context 826 829 ) 830 + // Sending a no-op font size action marks the Ghostty surface as 831 + // "font_size_adjusted", which prevents config reloads (triggered by 832 + // keybind changes on worktree switch) from resetting the font to the 833 + // config default. 834 + if resolvedFontSize != nil { 835 + view.performBindingAction("increase_font_size:0") 836 + } 827 837 configureBridgeCallbacks(for: view, tabId: tabId) 828 838 configureSurfaceCallbacks(for: view, tabId: tabId) 829 839 surfaces[view.id] = view ··· 863 873 guard let self else { return } 864 874 self.updateRunningState(for: tabId) 865 875 } 866 - view.bridge.onCellSizeChange = { [weak self, weak view] in 867 - guard let self, let view else { return } 868 - self.handleCellSizeChange(forSurfaceID: view.id) 869 - } 870 - view.bridge.onConfigChange = { [weak self, weak view] in 871 - guard let self, let view else { return } 872 - self.handleCellSizeChange(forSurfaceID: view.id) 873 - } 874 876 view.bridge.onDesktopNotification = { [weak self, weak view] title, body in 875 877 guard let self, let view else { return } 876 878 self.appendNotification(title: title, body: body, surfaceId: view.id) ··· 903 905 self.recordKeyInput(forSurfaceID: view.id) 904 906 self.markNotificationsRead(forSurfaceID: view.id) 905 907 } 906 - view.onResetFontSizeShortcut = { [weak self] in 908 + view.onFontSizeShortcut = { [weak self] in 907 909 guard let self else { return } 908 - self.onFontSizeChanged?(nil) 910 + self.onFontSizeAdjusted?() 909 911 } 910 912 } 911 913 ··· 975 977 return focusedSurfaceIdByTab[selectedTabId] 976 978 } 977 979 978 - private func handleCellSizeChange(forSurfaceID surfaceID: UUID) { 979 - handleCellSizeChange(forSurfaceID: surfaceID, fontSize: fontSize(forSurfaceID: surfaceID)) 980 - } 981 - 982 - func handleCellSizeChange(forSurfaceID surfaceID: UUID, fontSize: Float32?) { 983 - let inserted = hasInitializedCellSizeSurfaceIDs.insert(surfaceID).inserted 984 - guard !inserted else { return } 985 - onFontSizeChanged?(fontSize) 986 - } 987 - 988 - private func fontSize(forSurfaceID surfaceID: UUID) -> Float32? { 989 - inheritedSurfaceConfig(fromSurfaceId: surfaceID, context: GHOSTTY_SURFACE_CONTEXT_TAB).fontSize 990 - } 991 - 992 980 private func handlePromptTitle( 993 981 _ promptType: ghostty_action_prompt_title_e, 994 982 tabId: TerminalTabID ··· 1238 1226 for surface in tree.leaves() { 1239 1227 surface.closeSurface() 1240 1228 surfaces.removeValue(forKey: surface.id) 1241 - hasInitializedCellSizeSurfaceIDs.remove(surface.id) 1242 1229 } 1243 1230 focusedSurfaceIdByTab.removeValue(forKey: tabId) 1244 1231 tabIsRunningById.removeValue(forKey: tabId) ··· 1364 1351 guard let tabId = tabId(containing: view.id), let tree = trees[tabId] else { 1365 1352 view.closeSurface() 1366 1353 surfaces.removeValue(forKey: view.id) 1367 - hasInitializedCellSizeSurfaceIDs.remove(view.id) 1368 1354 return 1369 1355 } 1370 1356 guard let node = tree.find(id: view.id) else { 1371 1357 view.closeSurface() 1372 1358 surfaces.removeValue(forKey: view.id) 1373 - hasInitializedCellSizeSurfaceIDs.remove(view.id) 1374 1359 return 1375 1360 } 1376 1361 let nextSurface = ··· 1380 1365 let newTree = tree.removing(node) 1381 1366 view.closeSurface() 1382 1367 surfaces.removeValue(forKey: view.id) 1383 - hasInitializedCellSizeSurfaceIDs.remove(view.id) 1384 1368 if newTree.isEmpty { 1385 1369 trees.removeValue(forKey: tabId) 1386 1370 focusedSurfaceIdByTab.removeValue(forKey: tabId)
+12 -6
supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift
··· 143 143 var onKeyInput: (() -> Void)? 144 144 var onCommittedText: ((String) -> Void)? 145 145 var onMirroredKey: ((MirroredTerminalKey) -> Void)? 146 - var onResetFontSizeShortcut: (() -> Void)? 146 + var onFontSizeShortcut: (() -> Void)? 147 147 148 148 private var accessibilityPaneIndexHelp: String? 149 149 ··· 1014 1014 1015 1015 override func performKeyEquivalent(with event: NSEvent) -> Bool { 1016 1016 guard event.type == .keyDown else { return false } 1017 - let isResetFontSizeShortcut = matchesBindingShortcut(event: event, action: "reset_font_size") 1017 + let isFontSizeShortcut = matchesFontSizeShortcut(event: event) 1018 1018 guard let surface else { return false } 1019 1019 guard focused else { return false } 1020 1020 ··· 1033 1033 return true 1034 1034 } 1035 1035 keyDown(with: event) 1036 - if isResetFontSizeShortcut { 1037 - onResetFontSizeShortcut?() 1036 + if isFontSizeShortcut { 1037 + onFontSizeShortcut?() 1038 1038 } 1039 1039 // Ghostty handled paste internally; broadcast the pasted text to followers. 1040 1040 if onCommittedText != nil, ··· 1067 1067 return false 1068 1068 } 1069 1069 keyDown(with: finalEvent) 1070 - if isResetFontSizeShortcut { 1071 - onResetFontSizeShortcut?() 1070 + if isFontSizeShortcut { 1071 + onFontSizeShortcut?() 1072 1072 } 1073 1073 return true 1074 + } 1075 + 1076 + private func matchesFontSizeShortcut(event: NSEvent) -> Bool { 1077 + matchesBindingShortcut(event: event, action: "reset_font_size") 1078 + || matchesBindingShortcut(event: event, action: "increase_font_size:1") 1079 + || matchesBindingShortcut(event: event, action: "decrease_font_size:1") 1074 1080 } 1075 1081 1076 1082 private func matchesBindingShortcut(event: NSEvent, action: String) -> Bool {
+2 -8
supacodeTests/AppShortcutsTests.swift
··· 21 21 @Test func worktreeSelectionUsesControlNumberShortcuts() { 22 22 expectNoDifference( 23 23 AppShortcuts.worktreeSelection.map(\.display), 24 - ["⌃1", "⌃2", "⌃3", "⌃4", "⌃5", "⌃6", "⌃7", "⌃8", "⌃9", "⌃0"] 24 + ["⌃1", "⌃2", "⌃3", "⌃4", "⌃5", "⌃6", "⌃7", "⌃8", "⌃9"] 25 25 ) 26 26 27 27 for shortcut in AppShortcuts.worktreeSelection { ··· 32 32 @Test func terminalTabSelectionUsesCommandNumberShortcuts() { 33 33 expectNoDifference( 34 34 AppShortcuts.terminalTabSelection.map(\.display), 35 - ["⌘1", "⌘2", "⌘3", "⌘4", "⌘5", "⌘6", "⌘7", "⌘8", "⌘9", "⌘0"] 35 + ["⌘1", "⌘2", "⌘3", "⌘4", "⌘5", "⌘6", "⌘7", "⌘8", "⌘9"] 36 36 ) 37 - 38 - for shortcut in AppShortcuts.terminalTabSelection { 39 - #expect(shortcut.modifiers == .command) 40 - } 41 37 } 42 38 43 39 @Test func selectionDisplayUsesResolvedOverrides() { ··· 111 107 "selectTerminalTab7=\(AppShortcuts.selectTerminalTab7.display)", 112 108 "selectTerminalTab8=\(AppShortcuts.selectTerminalTab8.display)", 113 109 "selectTerminalTab9=\(AppShortcuts.selectTerminalTab9.display)", 114 - "selectTerminalTab0=\(AppShortcuts.selectTerminalTab0.display)", 115 110 "selectPreviousTerminalTab=\(AppShortcuts.selectPreviousTerminalTab.display)", 116 111 "selectNextTerminalTab=\(AppShortcuts.selectNextTerminalTab.display)", 117 112 "selectPreviousTerminalPane=\(AppShortcuts.selectPreviousTerminalPane.display)", ··· 139 134 "selectTerminalTab7=⌘7", 140 135 "selectTerminalTab8=⌘8", 141 136 "selectTerminalTab9=⌘9", 142 - "selectTerminalTab0=⌘0", 143 137 "selectPreviousTerminalTab=⌘⇧[", 144 138 "selectNextTerminalTab=⌘⇧]", 145 139 "selectPreviousTerminalPane=⌘[",
+9 -106
supacodeTests/WorktreeTerminalManagerTests.swift
··· 46 46 #expect(event == .setupScriptConsumed(worktreeID: worktree.id)) 47 47 } 48 48 49 - @Test func fontSizeResetToBaselineEmitsNilOverride() async { 50 - let runtime = GhosttyRuntime() 51 - let baseline = runtime.defaultFontSize() 52 - let manager = WorktreeTerminalManager(runtime: runtime, preferredFontSize: baseline + 1) 53 - let worktree = makeWorktree() 54 - let state = manager.state(for: worktree) 49 + @Test func syncPreferredFontSizeNoOpForMissingState() async { 50 + let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) 55 51 let stream = manager.eventStream() 56 52 var iterator = stream.makeAsyncIterator() 57 53 58 - state.onFontSizeChanged?(baseline) 54 + manager.syncPreferredFontSize(from: "/nonexistent") 59 55 60 - var event: TerminalClient.Event? 61 - while let next = await iterator.next() { 62 - if case .fontSizeChanged = next { 63 - event = next 64 - break 65 - } 66 - } 67 - 68 - #expect(event == .fontSizeChanged(nil)) 56 + // Should not emit font event; only the notification indicator event 57 + let first = await iterator.next() 58 + #expect(first == .notificationIndicatorChanged(count: 0)) 69 59 } 70 60 71 - @Test func duplicateFontSizeChangeIsDeduplicated() async { 72 - let runtime = GhosttyRuntime() 73 - let baseline = runtime.defaultFontSize() 74 - let firstSize = baseline + 2 75 - let secondSize = baseline + 3 76 - let manager = WorktreeTerminalManager(runtime: runtime) 61 + @Test func onFontSizeAdjustedCallbackIsWired() { 62 + let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) 77 63 let worktree = makeWorktree() 78 64 let state = manager.state(for: worktree) 79 - let stream = manager.eventStream() 80 - var iterator = stream.makeAsyncIterator() 81 65 82 - state.onFontSizeChanged?(firstSize) 83 - state.onFontSizeChanged?(firstSize) 84 - state.onFontSizeChanged?(secondSize) 85 - 86 - var fontSizeEvents: [TerminalClient.Event] = [] 87 - while let next = await iterator.next() { 88 - if case .fontSizeChanged = next { 89 - fontSizeEvents.append(next) 90 - } 91 - if fontSizeEvents.count == 2 { 92 - break 93 - } 94 - } 95 - 96 - #expect(fontSizeEvents == [.fontSizeChanged(firstSize), .fontSizeChanged(secondSize)]) 97 - } 98 - 99 - @Test func explicitResetAcrossStatesEmitsNilOverride() async { 100 - let runtime = GhosttyRuntime() 101 - let baseline = runtime.defaultFontSize() 102 - let manager = WorktreeTerminalManager(runtime: runtime, preferredFontSize: baseline + 2) 103 - let worktreeA = Worktree( 104 - id: "/tmp/repo-a/wt-a", 105 - name: "wt-a", 106 - detail: "detail", 107 - workingDirectory: URL(fileURLWithPath: "/tmp/repo-a/wt-a"), 108 - repositoryRootURL: URL(fileURLWithPath: "/tmp/repo-a") 109 - ) 110 - let worktreeB = Worktree( 111 - id: "/tmp/repo-b/wt-b", 112 - name: "wt-b", 113 - detail: "detail", 114 - workingDirectory: URL(fileURLWithPath: "/tmp/repo-b/wt-b"), 115 - repositoryRootURL: URL(fileURLWithPath: "/tmp/repo-b") 116 - ) 117 - _ = manager.state(for: worktreeA) 118 - _ = manager.state(for: worktreeB) 119 - 120 - let stream = manager.eventStream() 121 - var iterator = stream.makeAsyncIterator() 122 - 123 - manager.resetFontSizeAcrossStates() 124 - 125 - var event: TerminalClient.Event? 126 - while let next = await iterator.next() { 127 - if case .fontSizeChanged = next { 128 - event = next 129 - break 130 - } 131 - } 132 - 133 - #expect(event == .fontSizeChanged(nil)) 134 - } 135 - 136 - @Test func explicitResetWhenAlreadyDefaultDoesNotEmitEvent() async { 137 - let manager = WorktreeTerminalManager(runtime: GhosttyRuntime(), preferredFontSize: nil) 138 - _ = manager.state(for: makeWorktree()) 139 - let stream = manager.eventStream() 140 - var iterator = stream.makeAsyncIterator() 141 - 142 - manager.resetFontSizeAcrossStates() 143 - 144 - let first = await iterator.next() 145 - #expect(first == .notificationIndicatorChanged(count: 0)) 146 - } 147 - 148 - @Test func cellSizeChangeSkipsFirstEventPerSurface() { 149 - let state = WorktreeTerminalState(runtime: GhosttyRuntime(), worktree: makeWorktree()) 150 - var captured: [Float32?] = [] 151 - let firstSurface = UUID() 152 - let secondSurface = UUID() 153 - 154 - state.onFontSizeChanged = { fontSize in 155 - captured.append(fontSize) 156 - } 157 - 158 - state.handleCellSizeChange(forSurfaceID: firstSurface, fontSize: 13) 159 - state.handleCellSizeChange(forSurfaceID: firstSurface, fontSize: 14) 160 - state.handleCellSizeChange(forSurfaceID: secondSurface, fontSize: 15) 161 - state.handleCellSizeChange(forSurfaceID: secondSurface, fontSize: 16) 162 - 163 - #expect(captured == [14, 16]) 66 + #expect(state.onFontSizeAdjusted != nil) 164 67 } 165 68 166 69 @Test func notificationIndicatorUsesCurrentCountOnStreamStart() async {