From 517a4e4fdc17ba5028dc78e6dbeaeda10cd506fb Mon Sep 17 00:00:00 2001 From: kua-agent Date: Tue, 1 Sep 2026 22:20:40 +0400 Subject: [PATCH] fix: load persisted capture region at launch; REGION-PERSIST selftest phase Ben-reported: picker opened on every activation. AppModel.init set region = nil and never read UserDefaults back; saving worked, every launch forgot it. init now loads via loadPersistedRegion() (decode + isStillValid). Selftest phase 2 writes a known region, reloads through the same path, asserts the rect, restores the user's stored value. Coordinator ran it: PICKER-SELFTEST PASS + REGION-PERSIST PASS, 90/90 tests green. Co-Authored-By: Claude Fable 5 --- Sources/Shotdeck/AppModel.swift | 10 ++++++- Sources/Shotdeck/PickerSelfTest.swift | 43 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Sources/Shotdeck/AppModel.swift b/Sources/Shotdeck/AppModel.swift index 6c95b5a..050e971 100644 --- a/Sources/Shotdeck/AppModel.swift +++ b/Sources/Shotdeck/AppModel.swift @@ -72,7 +72,7 @@ public final class AppModel { captures: [], pdfFileName: nil ) - self.region = nil + self.region = Self.loadPersistedRegion() self.screenRecordingGranted = ScreenCapturer.isScreenRecordingGranted // Seeded from FolderSettings.resolve() via resolvedAppSupportPaths — never .standard(). let folders = FolderSettings.resolve() @@ -237,6 +237,14 @@ public final class AppModel { NSWorkspace.shared.open(url) } + static func loadPersistedRegion() -> CaptureRegion? { + guard let data = UserDefaults.standard.data(forKey: CaptureRegion.defaultsKey), + let decoded = try? JSONDecoder().decode(CaptureRegion.self, from: data), + decoded.isStillValid + else { return nil } + return decoded + } + private func persistRegion(_ picked: CaptureRegion) { replaceRegion(picked) if let encoded = try? JSONEncoder().encode(picked) { diff --git a/Sources/Shotdeck/PickerSelfTest.swift b/Sources/Shotdeck/PickerSelfTest.swift index f389d43..c1eaede 100644 --- a/Sources/Shotdeck/PickerSelfTest.swift +++ b/Sources/Shotdeck/PickerSelfTest.swift @@ -111,9 +111,52 @@ enum PickerSelfTest { print("PICKER-SELFTEST PASS rect=\(format(got.rect))") fflush(stdout) + + runRegionPersistPhase() exit(0) } + /// Phase 2: writes a known region under `CaptureRegion.defaultsKey`, reloads it through + /// `AppModel.loadPersistedRegion()` (the same path init uses), then restores whatever + /// value was stored before so a real picked region is untouched. + private static func runRegionPersistPhase() { + let defaults = UserDefaults.standard + let previous = defaults.data(forKey: CaptureRegion.defaultsKey) + + let known = CaptureRegion( + displayID: CGMainDisplayID(), + rect: CGRect(x: 10, y: 10, width: 100, height: 100), + capturedScale: 2.0 + ) + var failure: String? + if let encoded = try? JSONEncoder().encode(known) { + defaults.set(encoded, forKey: CaptureRegion.defaultsKey) + if let loaded = AppModel.loadPersistedRegion() { + if loaded.rect != known.rect { + failure = "expected=\(format(known.rect)) got=\(format(loaded.rect))" + } + } else { + failure = "loadPersistedRegion returned nil" + } + } else { + failure = "could not encode CaptureRegion" + } + + if let previous { + defaults.set(previous, forKey: CaptureRegion.defaultsKey) + } else { + defaults.removeObject(forKey: CaptureRegion.defaultsKey) + } + + if let failure { + print("REGION-PERSIST FAIL \(failure)") + fflush(stdout) + exit(1) + } + print("REGION-PERSIST PASS") + fflush(stdout) + } + private static func interpolate(_ step: Int) -> NSPoint { let t = CGFloat(step) / CGFloat(dragSteps) return NSPoint(