diff --git a/Sources/CodexBar/MenuBarStatusItemDefaultsRepair.swift b/Sources/CodexBar/MenuBarStatusItemDefaultsRepair.swift index 357ba3e4d1..5a3b5e3c08 100644 --- a/Sources/CodexBar/MenuBarStatusItemDefaultsRepair.swift +++ b/Sources/CodexBar/MenuBarStatusItemDefaultsRepair.swift @@ -6,9 +6,10 @@ enum MenuBarStatusItemDefaultsRepair { private static let legacyAutosavePrefix = "codexbar-" static func repairHiddenVisibilityDefaultsIfNeeded(defaults: UserDefaults) -> [String] { + let didRepair = defaults.bool(forKey: self.didRepairKey) let repairedKeys = defaults.dictionaryRepresentation().keys .filter { key in - self.shouldRepair(key: key, value: defaults.object(forKey: key)) + self.shouldRepair(key: key, value: defaults.object(forKey: key), didRepair: didRepair) } .sorted() @@ -21,10 +22,11 @@ enum MenuBarStatusItemDefaultsRepair { return repairedKeys } - static func shouldRepair(key: String, value: Any?) -> Bool { + static func shouldRepair(key: String, value: Any?, didRepair: Bool = false) -> Bool { guard key.hasPrefix(self.visibilityPrefix), self.isFalse(value) else { return false } let itemName = String(key.dropFirst(self.visibilityPrefix.count)) - return itemName.hasPrefix(self.legacyAutosavePrefix) || self.isDefaultStatusItemName(itemName) + if self.isDefaultStatusItemName(itemName) { return true } + return !didRepair && itemName.hasPrefix(self.legacyAutosavePrefix) } private static func isDefaultStatusItemName(_ itemName: String) -> Bool { diff --git a/Tests/CodexBarTests/StatusItemControllerSplitLifecycleTests.swift b/Tests/CodexBarTests/StatusItemControllerSplitLifecycleTests.swift index a26b0fcd9d..955f761b1e 100644 --- a/Tests/CodexBarTests/StatusItemControllerSplitLifecycleTests.swift +++ b/Tests/CodexBarTests/StatusItemControllerSplitLifecycleTests.swift @@ -329,7 +329,7 @@ struct StatusItemControllerSplitLifecycleTests { } @Test - func `status item defaults repair removes stale hidden Control Center keys repeatedly`() throws { + func `status item defaults repair removes stale hidden Control Center keys during migration`() throws { let suite = "StatusItemControllerSplitLifecycleTests-repair-repeated-\(UUID().uuidString)" let defaults = try #require(UserDefaults(suiteName: suite)) defaults.removePersistentDomain(forName: suite) @@ -351,16 +351,26 @@ struct StatusItemControllerSplitLifecycleTests { #expect(defaults.object(forKey: "NSStatusItem VisibleCC Item-12") == nil) #expect(defaults.object(forKey: "NSStatusItem VisibleCC codexbar-merged") == nil) #expect(defaults.bool(forKey: MenuBarStatusItemDefaultsRepair.didRepairKey)) + } + + @Test + func `status item defaults repair repeats default item cleanup without overriding later user hides`() throws { + let suite = "StatusItemControllerSplitLifecycleTests-repair-repeat-default-\(UUID().uuidString)" + let defaults = try #require(UserDefaults(suiteName: suite)) + defaults.removePersistentDomain(forName: suite) + defaults.set(true, forKey: MenuBarStatusItemDefaultsRepair.didRepairKey) + defer { + defaults.removePersistentDomain(forName: suite) + } defaults.set(false, forKey: "NSStatusItem VisibleCC Item-2") defaults.set(false, forKey: "NSStatusItem VisibleCC codexbar-codex") #expect(MenuBarStatusItemDefaultsRepair.repairHiddenVisibilityDefaultsIfNeeded(defaults: defaults) == [ "NSStatusItem VisibleCC Item-2", - "NSStatusItem VisibleCC codexbar-codex", ]) #expect(defaults.object(forKey: "NSStatusItem VisibleCC Item-2") == nil) - #expect(defaults.object(forKey: "NSStatusItem VisibleCC codexbar-codex") == nil) + #expect(defaults.object(forKey: "NSStatusItem VisibleCC codexbar-codex") != nil) } @Test