From bc99a32994ade537b1a76ea8abc4145994956193 Mon Sep 17 00:00:00 2001 From: maxthegray Date: Tue, 28 Jul 2026 01:09:51 -0400 Subject: [PATCH 1/4] Refine Smart Perch onboarding copy --- Sources/Perch/UI/SettingsView.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Perch/UI/SettingsView.swift b/Sources/Perch/UI/SettingsView.swift index 39e6498..f677bc8 100644 --- a/Sources/Perch/UI/SettingsView.swift +++ b/Sources/Perch/UI/SettingsView.swift @@ -37,16 +37,16 @@ struct GeneralSettingsPane: View { } .formStyle(.grouped) .onDisappear { versionClicks = 0 } - .alert("wanna try smart perch?", isPresented: $showsSmartPerchPrompt) { - Button("yep, update me") { + .alert("Try Smart Perch?", isPresented: $showsSmartPerchPrompt) { + Button("Join and Update") { Updater.shared.joinSmartPerch() } - Button("not right now", role: .cancel) {} + Button("Not Now", role: .cancel) {} } message: { Text( - "it can name screenshots and remember where you usually put things. " - + "everything it learns stays on this mac. this switches you over " - + "to the smart perch updates." + "Smart Perch names screenshots and remembers where you usually put " + + "things. Everything it learns stays on this Mac. Joining switches " + + "Perch to the Smart Perch update track." ) } } From 11614d0f1d3ad8a6616dd98d420e27b3bc1dcec4 Mon Sep 17 00:00:00 2001 From: maxthegray Date: Tue, 28 Jul 2026 01:12:43 -0400 Subject: [PATCH 2/4] Use Smart Perch release title --- Scripts/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/release.sh b/Scripts/release.sh index 8e6efac..5254917 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -37,8 +37,8 @@ case "${TRACK}" in NOTARY_ZIP="/tmp/Perch-Smart-notary.zip" ASSET_NAME="Perch-Smart.zip" TARGET_BRANCH="smart-perch" - FEED_TITLE="Perch Smart" - RELEASE_TITLE="Perch Smart ${VERSION}" + FEED_TITLE="Smart Perch" + RELEASE_TITLE="Smart Perch ${VERSION}" RELEASE_FLAGS=(--prerelease) ;; *) From a346141490ed16d9e627ff44adc141fe93f78133 Mon Sep 17 00:00:00 2001 From: maxthegray Date: Tue, 28 Jul 2026 01:22:19 -0400 Subject: [PATCH 3/4] Streamline drag reminders and snapping --- Sources/Perch/App/ShelfController.swift | 53 ++++++++++++++++--------- Sources/Perch/UI/SettingsView.swift | 7 ---- Sources/Perch/UI/ShelfHostView.swift | 4 -- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Sources/Perch/App/ShelfController.swift b/Sources/Perch/App/ShelfController.swift index b06ccb9..596f7d1 100644 --- a/Sources/Perch/App/ShelfController.swift +++ b/Sources/Perch/App/ShelfController.swift @@ -38,6 +38,10 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { private var dismissingFreeShelfResetTask: Task? /// True while a system drag is in flight; grows the empty drop target. private var dragActive = false + /// Delays the visual edge tab so short, ordinary drags stay quiet. If a drag lasts + /// long enough, the nearest tab appears as a reminder that Perch is available. + private var edgeTabReminderTask: Task? + private var edgeTabReminderVisible = false /// True when the current drag (in "reveal while dragging" mode) is what opened the /// shelf, so it follows the nearest edge during the drag and retracts on drag-end if /// nothing was dropped onto it. @@ -58,10 +62,6 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { private var keepsEmptyFreeShelf: Bool { UserDefaults.standard.object(forKey: ShelfHostView.keepEmptyShelfKey) as? Bool ?? true } - /// Whether free shelves preview and snap back into enabled edge docks. - private var snapBackToEdgesEnabled: Bool { - UserDefaults.standard.object(forKey: ShelfHostView.snapBackToEdgesKey) as? Bool ?? true - } /// Polls the cursor while the shelf is open so an empty shelf reliably retracts once /// the pointer leaves — see `startRetractWatcher`. private var retractWatcher: Task? @@ -331,28 +331,24 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { Task { @MainActor in self?.rebuildEdgeStrips() } } - // During a drag, show only the tab nearest the cursor; hide all when it ends. - // The drag also grows the empty drop target into a bigger, easier box. + // During a longer drag, show the nearest tab as a reminder; short drags stay + // visually quiet. The drag also grows the empty drop target into an easier box. mouseMonitor.onDragSessionChange = { [weak self] active in guard let self else { return } self.setDragActive(active) if active { - if self.usesEdgeDock { - self.showNearestTab(to: NSEvent.mouseLocation) - } else { - self.setTabsShown(false) - } + self.beginEdgeTabReminder() if self.usesEdgeDock, self.revealOnDragStart { self.revealForDrag(to: NSEvent.mouseLocation) } } else { - self.setTabsShown(false) + self.endEdgeTabReminder() self.dragDidEnd() } } mouseMonitor.onDragMoved = { [weak self] point in guard let self else { return } - if self.usesEdgeDock { + if self.usesEdgeDock, self.edgeTabReminderVisible { self.showNearestTab(to: point) } else { self.setTabsShown(false) @@ -917,6 +913,31 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { } } + private func beginEdgeTabReminder() { + edgeTabReminderTask?.cancel() + edgeTabReminderVisible = false + setTabsShown(false) + guard usesEdgeDock else { return } + + edgeTabReminderTask = Task { @MainActor [weak self] in + do { + try await Task.sleep(for: .milliseconds(1200)) + } catch { + return + } + guard let self, self.dragActive, self.usesEdgeDock else { return } + self.edgeTabReminderVisible = true + self.showNearestTab(to: NSEvent.mouseLocation) + } + } + + private func endEdgeTabReminder() { + edgeTabReminderTask?.cancel() + edgeTabReminderTask = nil + edgeTabReminderVisible = false + setTabsShown(false) + } + private func setDragActive(_ active: Bool) { dragActive = active // Ghost rows hide for the drag's duration so the drop geometry never shifts @@ -1112,10 +1133,6 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { /// free also gets a chance to re-dock at any enabled edge. private func shelfDragDidEnd() { if revealMode == .free { - guard snapBackToEdgesEnabled else { - clearDockSnapPreview() - return - } let target = refreshedPreviewedDockTarget() ?? nearestDockSnapTarget() if let target { snapFreeShelf(to: target) @@ -1269,7 +1286,7 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { /// Reveal the exact landing frame while a free shelf is inside the snap radius. /// Moving away or dragging a still-docked shelf removes the preview immediately. private func updateDockSnapPreview() { - guard revealMode == .free, snapBackToEdgesEnabled else { + guard revealMode == .free else { clearDockSnapPreview() return } diff --git a/Sources/Perch/UI/SettingsView.swift b/Sources/Perch/UI/SettingsView.swift index f677bc8..cbe925c 100644 --- a/Sources/Perch/UI/SettingsView.swift +++ b/Sources/Perch/UI/SettingsView.swift @@ -224,7 +224,6 @@ struct BehaviorSettingsPane: View { @AppStorage(ShelfHostView.shakeToSummonKey) private var shakeToSummon = true @AppStorage(ShelfHostView.revealOnDragStartKey) private var revealOnDragStart = true @AppStorage(ShelfHostView.keepEmptyShelfKey) private var keepEmptyShelf = true - @AppStorage(ShelfHostView.snapBackToEdgesKey) private var snapBackToEdges = true @AppStorage(DockGeometryReader.enabledKey) private var snapBesideDock = false @AppStorage(ShelfHostView.vendCopiesKey) private var vendCopies = false @AppStorage(RecentArrivals.enabledKey) private var offerRecentArrivals = true @@ -252,12 +251,6 @@ struct BehaviorSettingsPane: View { isOn: $keepEmptyShelf ) draggingModeRow - behaviorRow( - demo: .moveShelf, - title: "Snap to locations", - caption: "Release a free shelf near an enabled screen edge or Dock side.", - isOn: $snapBackToEdges - ) } Section { diff --git a/Sources/Perch/UI/ShelfHostView.swift b/Sources/Perch/UI/ShelfHostView.swift index 0c12bd2..eefe9f9 100644 --- a/Sources/Perch/UI/ShelfHostView.swift +++ b/Sources/Perch/UI/ShelfHostView.swift @@ -97,10 +97,6 @@ final class ShelfHostView: NSView, QLPreviewPanelDataSource, QLPreviewPanelDeleg /// (dragged out or deleted), showing the empty drop tile, instead of dismissing /// itself. Read live by the controller. Default true. static let keepEmptyShelfKey = "Perch.KeepEmptyShelf" - /// When true, releasing a free shelf near an enabled dock previews the target and - /// snaps it back into ordinary edge behavior. Default true. - static let snapBackToEdgesKey = "Perch.SnapBackToEdges" - /// Called with the SwiftUI content's measured natural height so the controller can /// size the window to fit. var onContentHeight: ((CGFloat) -> Void)? From beea0e260cf6de38d2358209d01d0c3366a07085 Mon Sep 17 00:00:00 2001 From: maxthegray Date: Tue, 28 Jul 2026 01:31:34 -0400 Subject: [PATCH 4/4] Delay the drag reminder --- Sources/Perch/App/ShelfController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Perch/App/ShelfController.swift b/Sources/Perch/App/ShelfController.swift index 596f7d1..e5b648c 100644 --- a/Sources/Perch/App/ShelfController.swift +++ b/Sources/Perch/App/ShelfController.swift @@ -921,7 +921,7 @@ final class ShelfController: ShelfDropHandling, EdgeStripDelegate { edgeTabReminderTask = Task { @MainActor [weak self] in do { - try await Task.sleep(for: .milliseconds(1200)) + try await Task.sleep(for: .seconds(5)) } catch { return }