native macOS codings agent orchestrator
6
fork

Configure Feed

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

refactor(tab-icon): collapse two icon-lock bools into a single enum

Replace `isIconLocked` / `isScriptIconActive` (which together encoded
three valid states plus one impossible one — `(true, true)`) with a
`TerminalTabIconLock` enum: `.auto < .script < .user`. The precedence
chain is now expressed in the type, and impossible states are
unrepresentable.

Use `.auto` instead of the more obvious `.none`: at call sites like
`tab?.iconLock == .none`, Swift would otherwise infer the right-hand
side as `Optional.none` and silently compare against `nil` — caught
by the existing `clearIconOverride*` tests once we used the enum
through an optional chain.

`overrideIcon` and `clearIconOverride` no longer need to clear
sibling flags by hand — assigning the new state replaces the whole
field.

onevcat 1826028c 7f673175

+47 -40
+21 -11
supacode/Features/Terminal/Models/TerminalTabItem.swift
··· 1 1 import Foundation 2 2 3 + /// Who currently owns a tab's icon slot. The precedence chain runs 4 + /// `auto < script < user`: stronger owners block weaker writes. 5 + /// 6 + /// - `auto`: nobody has claimed the icon — `CommandIconMap` and other 7 + /// auto-detection paths are free to overwrite it. 8 + /// - `script`: Run Script's `play.fill` or a Custom Command's configured 9 + /// icon. Survives auto-detection so the glyph doesn't flash mid-run. 10 + /// - `user`: the icon picker. Wins over everything until cleared. 11 + /// 12 + /// Avoid naming a case `.none` — it collides with `Optional.none` in 13 + /// expressions like `tab?.iconLock == .none`, where Swift would infer 14 + /// the right-hand side as the optional sentinel rather than this case. 15 + enum TerminalTabIconLock: Equatable, Sendable { 16 + case auto 17 + case script 18 + case user 19 + } 20 + 3 21 struct TerminalTabItem: Identifiable, Equatable, Sendable { 4 22 let id: TerminalTabID 5 23 var title: String 6 24 var icon: String? 7 25 var isDirty: Bool 8 26 var isTitleLocked: Bool 9 - var isIconLocked: Bool 10 - /// `true` while a Run Script or Custom Command's configured icon owns 11 - /// this tab's icon slot. Sits between auto-detected command icons and 12 - /// `isIconLocked` (user picker) in the precedence chain: blocks 13 - /// `CommandIconMap`-driven overrides so the play / configured glyph 14 - /// doesn't get clobbered mid-run, but yields to a user-set lock. 15 - var isScriptIconActive: Bool 27 + var iconLock: TerminalTabIconLock 16 28 17 29 init( 18 30 id: TerminalTabID = TerminalTabID(), ··· 20 32 icon: String?, 21 33 isDirty: Bool = false, 22 34 isTitleLocked: Bool = false, 23 - isIconLocked: Bool = false, 24 - isScriptIconActive: Bool = false 35 + iconLock: TerminalTabIconLock = .auto 25 36 ) { 26 37 self.id = id 27 38 self.title = title 28 39 self.icon = icon 29 40 self.isDirty = isDirty 30 41 self.isTitleLocked = isTitleLocked 31 - self.isIconLocked = isIconLocked 32 - self.isScriptIconActive = isScriptIconActive 42 + self.iconLock = iconLock 33 43 } 34 44 }
+13 -14
supacode/Features/Terminal/Models/TerminalTabManager.swift
··· 41 41 tabs[index].isTitleLocked = false 42 42 } 43 43 44 + /// Auto-detection write path (e.g. `CommandIconMap`). Only applies 45 + /// when nothing has claimed the icon slot. 44 46 func updateIcon(_ id: TerminalTabID, icon: String?) { 45 47 guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 46 - guard !tabs[index].isIconLocked, !tabs[index].isScriptIconActive else { return } 48 + guard tabs[index].iconLock == .auto else { return } 47 49 tabs[index].icon = icon 48 50 } 49 51 52 + /// User picker path. Always wins, transitioning the slot to `.user`. 50 53 func overrideIcon(_ id: TerminalTabID, icon: String) { 51 54 guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 52 55 tabs[index].icon = icon 53 - tabs[index].isIconLocked = true 54 - // User-set lock supersedes any active script-command override so the 55 - // precedence chain (user > script > auto) can't ambiguously stack. 56 - tabs[index].isScriptIconActive = false 56 + tabs[index].iconLock = .user 57 57 } 58 58 59 + /// "Reset to default" from the icon picker. Drops back to `.none` 60 + /// so the next auto-detected match can take over. 59 61 func clearIconOverride(_ id: TerminalTabID) { 60 62 guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 61 - tabs[index].isIconLocked = false 62 - // Reset to default also drops the script-level pin so the next 63 - // auto-detection can take over. 64 - tabs[index].isScriptIconActive = false 63 + tabs[index].iconLock = .auto 65 64 } 66 65 67 - /// Apply a Run Script / Custom Command icon and mark it as the active 68 - /// "script" override. No-op when the user has already locked an icon 69 - /// — manual lock always wins. 66 + /// Run Script / Custom Command write path. Pins the icon to `.script` 67 + /// — strong enough to block auto-detection, weak enough to yield to 68 + /// a user-set `.user` lock. 70 69 func setScriptIcon(_ id: TerminalTabID, icon: String) { 71 70 guard let index = tabs.firstIndex(where: { $0.id == id }) else { return } 72 - guard !tabs[index].isIconLocked else { return } 71 + guard tabs[index].iconLock != .user else { return } 73 72 tabs[index].icon = icon 74 - tabs[index].isScriptIconActive = true 73 + tabs[index].iconLock = .script 75 74 } 76 75 77 76 func updateDirty(_ id: TerminalTabID, isDirty: Bool) {
+4 -4
supacode/Features/Terminal/Models/WorktreeTerminalState.swift
··· 752 752 } 753 753 // Skip title/icon for blocking-script tabs as they are transient. 754 754 // Persist the icon only when the user has explicitly overridden it; otherwise 755 - // restore should pick up the current default ("terminal"). 755 + // restore should pick up the current default ("terminal") or auto-detection. 756 756 let isBlockingScriptTab = tab.id == runScriptTabId 757 - let snapshotIcon: String? = (isBlockingScriptTab || !tab.isIconLocked) ? nil : tab.icon 757 + let snapshotIcon: String? = (isBlockingScriptTab || tab.iconLock != .user) ? nil : tab.icon 758 758 snapshotTabs.append( 759 759 TerminalLayoutSnapshotPayload.SnapshotTab( 760 760 tabID: tab.id.rawValue.uuidString, ··· 848 848 title: entry.snapshotTab.title ?? "\(worktree.name) \(index + 1)", 849 849 icon: entry.snapshotTab.icon ?? "terminal", 850 850 isTitleLocked: entry.snapshotTab.title != nil, 851 - isIconLocked: entry.snapshotTab.icon != nil 851 + iconLock: entry.snapshotTab.icon != nil ? .user : .auto 852 852 ) 853 853 ) 854 854 } ··· 1528 1528 // user is currently looking at. 1529 1529 guard focusedSurfaceIdByTab[tabId] == surfaceId else { return } 1530 1530 guard let tab = tabManager.tabs.first(where: { $0.id == tabId }) else { return } 1531 - guard !tab.isIconLocked, !tab.isScriptIconActive else { return } 1531 + guard tab.iconLock == .auto else { return } 1532 1532 let serialised = icon.storageString 1533 1533 guard tab.icon != serialised else { return } 1534 1534 tabManager.updateIcon(tabId, icon: serialised)
+9 -11
supacodeTests/TerminalTabManagerTests.swift
··· 69 69 let tabId = manager.createTab(title: "one", icon: "terminal") 70 70 manager.overrideIcon(tabId, icon: "sparkles") 71 71 #expect(manager.tabs.first?.icon == "sparkles") 72 - #expect(manager.tabs.first?.isIconLocked == true) 72 + #expect(manager.tabs.first?.iconLock == .user) 73 73 } 74 74 75 75 @Test func updateIconRespectsLock() { ··· 85 85 let tabId = manager.createTab(title: "one", icon: "terminal") 86 86 manager.overrideIcon(tabId, icon: "sparkles") 87 87 manager.clearIconOverride(tabId) 88 - #expect(manager.tabs.first?.isIconLocked == false) 88 + #expect(manager.tabs.first?.iconLock == .auto) 89 89 manager.updateIcon(tabId, icon: "play.fill") 90 90 #expect(manager.tabs.first?.icon == "play.fill") 91 91 } ··· 95 95 let tabId = manager.createTab(title: "one", icon: "terminal") 96 96 manager.setScriptIcon(tabId, icon: "play.fill") 97 97 #expect(manager.tabs.first?.icon == "play.fill") 98 - #expect(manager.tabs.first?.isScriptIconActive == true) 99 - #expect(manager.tabs.first?.isIconLocked == false) 98 + #expect(manager.tabs.first?.iconLock == .script) 100 99 } 101 100 102 - @Test func updateIconRespectsScriptIconActive() { 101 + @Test func updateIconYieldsToScriptLock() { 103 102 let manager = TerminalTabManager() 104 103 let tabId = manager.createTab(title: "one", icon: "terminal") 105 104 manager.setScriptIcon(tabId, icon: "play.fill") ··· 107 106 #expect(manager.tabs.first?.icon == "play.fill") 108 107 } 109 108 110 - @Test func userOverrideClearsScriptIconActive() { 109 + @Test func userOverrideSupersedesScriptLock() { 111 110 let manager = TerminalTabManager() 112 111 let tabId = manager.createTab(title: "one", icon: "terminal") 113 112 manager.setScriptIcon(tabId, icon: "play.fill") 114 113 manager.overrideIcon(tabId, icon: "sparkles") 115 114 #expect(manager.tabs.first?.icon == "sparkles") 116 - #expect(manager.tabs.first?.isIconLocked == true) 117 - #expect(manager.tabs.first?.isScriptIconActive == false) 115 + #expect(manager.tabs.first?.iconLock == .user) 118 116 } 119 117 120 118 @Test func setScriptIconYieldsToUserLock() { ··· 123 121 manager.overrideIcon(tabId, icon: "sparkles") 124 122 manager.setScriptIcon(tabId, icon: "play.fill") 125 123 #expect(manager.tabs.first?.icon == "sparkles") 126 - #expect(manager.tabs.first?.isScriptIconActive == false) 124 + #expect(manager.tabs.first?.iconLock == .user) 127 125 } 128 126 129 - @Test func clearIconOverrideAlsoClearsScriptIconActive() { 127 + @Test func clearIconOverrideReleasesScriptLock() { 130 128 let manager = TerminalTabManager() 131 129 let tabId = manager.createTab(title: "one", icon: "terminal") 132 130 manager.setScriptIcon(tabId, icon: "play.fill") 133 131 manager.clearIconOverride(tabId) 134 - #expect(manager.tabs.first?.isScriptIconActive == false) 132 + #expect(manager.tabs.first?.iconLock == .auto) 135 133 manager.updateIcon(tabId, icon: "@asset:Npm") 136 134 #expect(manager.tabs.first?.icon == "@asset:Npm") 137 135 }