commit df715444409c0e5546b7ff6b98eb8087c591b511
parent d881c5950e4acb08a8cc7c4d64ec9241b06f2d86
Author: Michael Camilleri <[email protected]>
Date: Mon, 23 Feb 2026 16:05:01 +0900
Improve macOS menus
Co-Authored-By: Codex GPT 5.3 <[email protected]>
Diffstat:
7 files changed, 375 insertions(+), 20 deletions(-)
diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj
@@ -42,6 +42,7 @@
99977BFA37FBAAA49AF6B71E /* TaskStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */; };
99D17075DA3F00F52A18BB4D /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
A0AA8FD4C542E9AEB2437BC2 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
+ B7CFDCA5EA48EDE1C768FA21 /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C79ABB39A40D3E1828716C7 /* AppCommands.swift */; };
BA6953E0EFE6F8255F05A3FD /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
C169823665158AA347A63990 /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51B8129962E5CC78ECDDC2B /* TaskListView.swift */; };
C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
@@ -87,6 +88,7 @@
3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Drag.swift"; sourceTree = "<group>"; };
4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardNavigationModifier.swift; sourceTree = "<group>"; };
466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
+ 4C79ABB39A40D3E1828716C7 /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = "<group>"; };
4FC64B9F9370041BEDBD1E14 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
567DBAC2A39FA2760D006AAB /* PullToClear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToClear.swift; sourceTree = "<group>"; };
5A6D74EDDD7E3FC150064FB5 /* TaskListView+PullToClear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToClear.swift"; sourceTree = "<group>"; };
@@ -162,6 +164,7 @@
42CC0393E2524624AEC54D03 /* Helpers */ = {
isa = PBXGroup;
children = (
+ 4C79ABB39A40D3E1828716C7 /* AppCommands.swift */,
D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */,
9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */,
466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */,
@@ -460,6 +463,7 @@
buildActionMask = 2147483647;
files = (
99D17075DA3F00F52A18BB4D /* AccentColor.swift in Sources */,
+ B7CFDCA5EA48EDE1C768FA21 /* AppCommands.swift in Sources */,
DB5FF6C1AA57D4C9BDDD50FD /* ClickableTextField.swift in Sources */,
E4BD761E34CBB84CE80F7F49 /* CloudKitErrorClassifier.swift in Sources */,
E429067963379F99DD184FED /* CloudKitSyncMonitor.swift in Sources */,
@@ -554,6 +558,7 @@
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.mac;
+ PRODUCT_NAME = Listless;
SDKROOT = macosx;
};
name = Debug;
@@ -572,6 +577,7 @@
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.mac;
+ PRODUCT_NAME = Listless;
SDKROOT = macosx;
};
name = Release;
diff --git a/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme b/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
- version = "1.7">
+ version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
- buildImplicitDependencies = "YES"
- runPostActionsOnFailure = "NO">
+ buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -16,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0FB4F07A37999BBC6DFE4DBB"
- BuildableName = "Listless macOS.app"
+ BuildableName = "Listless.app"
BlueprintName = "Listless macOS"
ReferencedContainer = "container:Listless.xcodeproj">
</BuildableReference>
@@ -27,13 +26,12 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES"
- onlyGenerateCoverageForSpecifiedTargets = "NO">
+ shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0FB4F07A37999BBC6DFE4DBB"
- BuildableName = "Listless macOS.app"
+ BuildableName = "Listless.app"
BlueprintName = "Listless macOS"
ReferencedContainer = "container:Listless.xcodeproj">
</BuildableReference>
@@ -56,13 +54,11 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0FB4F07A37999BBC6DFE4DBB"
- BuildableName = "Listless macOS.app"
+ BuildableName = "Listless.app"
BlueprintName = "Listless macOS"
ReferencedContainer = "container:Listless.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
- <CommandLineArguments>
- </CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -75,13 +71,11 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0FB4F07A37999BBC6DFE4DBB"
- BuildableName = "Listless macOS.app"
+ BuildableName = "Listless.app"
BlueprintName = "Listless macOS"
ReferencedContainer = "container:Listless.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
- <CommandLineArguments>
- </CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/TaskListView+Logic.swift
@@ -256,6 +256,24 @@ extension TaskListView {
return .handled
}
+ func createNewTaskFromShortcut() -> KeyPress.Result {
+ createNewTask()
+ focusedField = nil
+ return .handled
+ }
+
+ func moveSelectedTaskUpFromShortcut() -> KeyPress.Result {
+ guard focusedField == .scrollView else { return .ignored }
+ moveSelectedTaskUp()
+ return .handled
+ }
+
+ func moveSelectedTaskDownFromShortcut() -> KeyPress.Result {
+ guard focusedField == .scrollView else { return .ignored }
+ moveSelectedTaskDown()
+ return .handled
+ }
+
func focusSelectedTask() -> KeyPress.Result {
guard focusedField == .scrollView else { return .ignored }
guard let currentID = selectedTaskID else { return .handled }
@@ -286,6 +304,39 @@ extension TaskListView {
return .handled
}
+ func moveSelectedTaskUp() {
+ guard focusedField == .scrollView else { return }
+ guard let currentID = selectedTaskID else { return }
+ guard let currentIndex = activeTasks.firstIndex(where: { $0.id == currentID }) else { return }
+ guard currentIndex > 0 else { return }
+
+ do {
+ try store.moveTask(taskID: currentID, toIndex: currentIndex - 1)
+ } catch {
+ presentStoreError(error)
+ }
+ }
+
+ func moveSelectedTaskDown() {
+ guard focusedField == .scrollView else { return }
+ guard let currentID = selectedTaskID else { return }
+ guard let currentIndex = activeTasks.firstIndex(where: { $0.id == currentID }) else { return }
+ guard currentIndex < activeTasks.count - 1 else { return }
+
+ do {
+ try store.moveTask(taskID: currentID, toIndex: currentIndex + 1)
+ } catch {
+ presentStoreError(error)
+ }
+ }
+
+ func markSelectedTaskCompleted() {
+ guard focusedField == .scrollView else { return }
+ guard let currentID = selectedTaskID else { return }
+ guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else { return }
+ toggleCompletion(task)
+ }
+
// MARK: - Focus Management
func focusTextField(_ taskID: UUID) {
diff --git a/ListlessMac/Helpers/AppCommands.swift b/ListlessMac/Helpers/AppCommands.swift
@@ -0,0 +1,26 @@
+import Foundation
+
+// Bridges SwiftUI view state to AppKit menu items without using SwiftUI's Commands API.
+@MainActor
+final class MenuCoordinator {
+ static let shared = MenuCoordinator()
+ private init() {}
+
+ // Actions — set by TaskListView on each relevant state change.
+ var newTask: (() -> Void)?
+ var deleteSelectedTask: (() -> Void)?
+ var moveSelectedTaskUp: (() -> Void)?
+ var moveSelectedTaskDown: (() -> Void)?
+ var markSelectedTaskCompleted: (() -> Void)?
+ var clearCompletedTasks: (() -> Void)?
+
+ // Enabled state — read by AppDelegate in menuWillOpen and validateMenuItem.
+ var canDeleteSelectedTask = false
+ var canMoveSelectedTaskUp = false
+ var canMoveSelectedTaskDown = false
+ var canMarkSelectedTaskCompleted = false
+ var canClearCompletedTasks = false
+
+ // Dynamic titles — read by AppDelegate in validateMenuItem.
+ var markCompletedTitle: String = "Mark as Completed"
+}
diff --git a/ListlessMac/ListlessMacApp.swift b/ListlessMac/ListlessMacApp.swift
@@ -1,22 +1,224 @@
import SwiftUI
+import AppKit
+
+private let customMenuTag = 1001
+
+@MainActor
+class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
+ func applicationDidFinishLaunching(_ notification: Notification) {
+ removeFormatMenu()
+
+ // SwiftUI builds menus asynchronously; watch for items appearing.
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(handleMenuDidAddItem(_:)),
+ name: NSMenu.didAddItemNotification,
+ object: nil
+ )
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(handleAppDidBecomeActive(_:)),
+ name: NSApplication.didBecomeActiveNotification,
+ object: nil
+ )
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(handleWindowDidBecomeKey(_:)),
+ name: NSWindow.didBecomeKeyNotification,
+ object: nil
+ )
+
+ // SwiftUI/AppKit can finish menu construction after launch callbacks; apply
+ // our menu patch repeatedly in the first moments to avoid startup races.
+ refreshMenus()
+ let startupDelays: [TimeInterval] = [0.0, 0.05, 0.15, 0.35]
+ for delay in startupDelays {
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
+ self?.refreshMenus()
+ }
+ }
+ }
+
+ deinit {
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ @objc private func handleMenuDidAddItem(_ notification: Notification) {
+ Task { @MainActor [weak self] in
+ self?.removeFormatMenu()
+ self?.setupFileMenuIfNeeded()
+ self?.setupEditMenuIfNeeded()
+ }
+ }
+
+ @objc private func handleAppDidBecomeActive(_ notification: Notification) {
+ refreshMenus()
+ }
+
+ @objc private func handleWindowDidBecomeKey(_ notification: Notification) {
+ refreshMenus()
+ }
+
+ private func refreshMenus() {
+ removeFormatMenu()
+ setupFileMenuIfNeeded()
+ setupEditMenuIfNeeded()
+ }
+
+ // MARK: - Menu Setup
+
+ @MainActor private func removeFormatMenu() {
+ guard let mainMenu = NSApp.mainMenu else { return }
+ mainMenu.items
+ .filter { $0.title == "Format" }
+ .forEach { mainMenu.removeItem($0) }
+ }
+
+ @MainActor private func setupFileMenuIfNeeded() {
+ guard let fileMenu = NSApp.mainMenu?.items.first(where: { $0.title == "File" })?.submenu else { return }
+
+ // Always update the New Window shortcut in-place — runs on every notification
+ // in case SwiftUI re-adds a fresh item. Never remove it; removal causes SwiftUI
+ // to re-add it at the bottom with the original shortcut.
+ if let newWindowItem = fileMenu.items.first(where: { $0.action == NSSelectorFromString("menuAction:") }) {
+ newWindowItem.keyEquivalent = "n"
+ newWindowItem.keyEquivalentModifierMask = [.command, .shift]
+ }
+
+ guard !fileMenu.items.contains(where: { $0.tag == customMenuTag }) else { return }
+
+ // Defer until both Close and Close All are present — the build order of these
+ // items is non-deterministic, so we wait for both before touching anything.
+ guard fileMenu.items.contains(where: { $0.action == NSSelectorFromString("performClose:") }),
+ fileMenu.items.contains(where: { $0.action == NSSelectorFromString("closeAll:") }) else { return }
+
+ guard let newWindowItem = fileMenu.items.first(where: { $0.action == NSSelectorFromString("menuAction:") }) else { return }
+ let insertIndex = fileMenu.index(of: newWindowItem)
+
+ // Insert sep1 and New Task before New Window, then sep2 immediately after it.
+ // We own both separators so the layout is stable regardless of where the
+ // system separator between Close/Close All ends up.
+ let sep1 = NSMenuItem.separator()
+ sep1.tag = customMenuTag
+ fileMenu.insertItem(sep1, at: insertIndex)
+
+ let newTaskItem = NSMenuItem(title: "New Task", action: #selector(handleNewTask), keyEquivalent: "n")
+ newTaskItem.keyEquivalentModifierMask = .command
+ newTaskItem.target = self
+ newTaskItem.tag = customMenuTag
+ fileMenu.insertItem(newTaskItem, at: insertIndex)
+
+ // New Window is now at insertIndex + 2; place sep2 immediately after it.
+ let sep2 = NSMenuItem.separator()
+ sep2.tag = customMenuTag
+ fileMenu.insertItem(sep2, at: insertIndex + 3)
+ }
+
+ @MainActor private func setupEditMenuIfNeeded() {
+ guard let editMenu = NSApp.mainMenu?.items.first(where: { $0.title == "Edit" })?.submenu else { return }
+
+ // NOTE: Any new menu key equivalents added in this file should also be mapped in
+ // TaskListView.keyboardNavigation(...). SwiftUI's onKeyPress layer can intercept
+ // events before AppKit key equivalents, so duplicating shortcut bindings at the
+ // SwiftUI layer keeps shortcut handling reliable.
+
+ // Modify the system "Delete" item in-place so it stays in its expected position
+ // but dispatches our action with our preferred shortcut (⌫). Runs on every
+ // notification in case SwiftUI re-adds a fresh item; left untagged so the guard
+ // below can still fire correctly on first setup.
+ if let systemDelete = editMenu.items.first(where: { $0.action == NSSelectorFromString("delete:") }) {
+ systemDelete.action = #selector(handleDeleteTask)
+ systemDelete.target = self
+ systemDelete.keyEquivalent = "\u{08}"
+ systemDelete.keyEquivalentModifierMask = []
+ }
+
+ guard !editMenu.items.contains(where: { $0.tag == customMenuTag }) else { return }
+
+ func addSep() {
+ let s = NSMenuItem.separator()
+ s.tag = customMenuTag
+ editMenu.addItem(s)
+ }
+
+ func addItem(title: String, action: Selector, key: String, modifiers: NSEvent.ModifierFlags) {
+ let i = NSMenuItem(title: title, action: action, keyEquivalent: key)
+ i.keyEquivalentModifierMask = modifiers
+ i.target = self
+ i.tag = customMenuTag
+ editMenu.addItem(i)
+ }
+
+ addSep()
+ addItem(title: "Move Up", action: #selector(handleMoveUp), key: "\u{F700}", modifiers: .command)
+ addItem(title: "Move Down", action: #selector(handleMoveDown), key: "\u{F701}", modifiers: .command)
+ addItem(title: "Mark as Completed", action: #selector(handleMarkCompleted), key: " ", modifiers: [])
+ addSep()
+ addItem(title: "Clear Completed", action: #selector(handleClearCompleted), key: "", modifiers: [])
+ }
+
+ // MARK: - NSMenuItemValidation
+ // AppKit calls this automatically for each item targeting self, both when the
+ // menu opens and when keyboard shortcuts are evaluated.
+
+ func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
+ let coord = MenuCoordinator.shared
+ switch menuItem.action {
+ case #selector(handleDeleteTask): return coord.canDeleteSelectedTask
+ case #selector(handleMoveUp): return coord.canMoveSelectedTaskUp
+ case #selector(handleMoveDown): return coord.canMoveSelectedTaskDown
+ case #selector(handleMarkCompleted):
+ menuItem.title = coord.markCompletedTitle
+ return coord.canMarkSelectedTaskCompleted
+ case #selector(handleClearCompleted): return coord.canClearCompletedTasks
+ default: return true
+ }
+ }
+
+ // MARK: - Actions
+
+ @objc private func handleNewTask() {
+ MenuCoordinator.shared.newTask?()
+ }
+
+ @objc private func handleDeleteTask() {
+ MenuCoordinator.shared.deleteSelectedTask?()
+ }
+
+ @objc private func handleMoveUp() {
+ MenuCoordinator.shared.moveSelectedTaskUp?()
+ }
+
+ @objc private func handleMoveDown() {
+ MenuCoordinator.shared.moveSelectedTaskDown?()
+ }
+
+ @objc private func handleMarkCompleted() {
+ MenuCoordinator.shared.markSelectedTaskCompleted?()
+ }
+
+ @objc private func handleClearCompleted() {
+ MenuCoordinator.shared.clearCompletedTasks?()
+ }
+}
@main
struct ListlessMacApp: App {
+ @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
private let persistenceController: PersistenceController
init() {
- // Use in-memory storage during UI tests
let isUITesting = ProcessInfo.processInfo.arguments.contains("UI_TESTING")
persistenceController = isUITesting ? PersistenceController(inMemory: true) : .shared
}
var body: some Scene {
- WindowGroup {
+ WindowGroup(id: "main") {
TaskListView(
store: TaskStore(persistenceController: persistenceController),
syncMonitor: persistenceController.syncMonitor
)
- .environment(\.managedObjectContext, persistenceController.viewContext)
+ .environment(\.managedObjectContext, persistenceController.viewContext)
}
.windowStyle(.hiddenTitleBar)
}
diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/TaskListView.swift
@@ -32,6 +32,75 @@ struct TaskListView: View {
@State var pullOffset: CGFloat = 0
var vStackSpacing: CGFloat { 0 }
+ var selectedIndex: Int? {
+ guard let currentID = selectedTaskID else { return nil }
+ return activeTasks.firstIndex(where: { $0.id == currentID })
+ }
+
+ var canDeleteSelectionFromList: Bool {
+ selectedTaskID != nil && focusedField == .scrollView
+ }
+
+ var canMarkSelectionCompleted: Bool {
+ guard focusedField == .scrollView else { return false }
+ guard let currentID = selectedTaskID else { return false }
+ return allTasksInDisplayOrder.contains(where: { $0.id == currentID })
+ }
+
+ var markCompletedMenuTitle: String {
+ guard let currentID = selectedTaskID,
+ let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }),
+ task.isCompleted else {
+ return "Mark as Completed"
+ }
+ return "Mark as Incomplete"
+ }
+
+ var canMoveSelectionUp: Bool {
+ guard focusedField == .scrollView else { return false }
+ guard let index = selectedIndex else { return false }
+ return index > 0
+ }
+
+ var canMoveSelectionDown: Bool {
+ guard focusedField == .scrollView else { return false }
+ guard let index = selectedIndex else { return false }
+ return index < activeTasks.count - 1
+ }
+
+ struct MenuState: Equatable {
+ let selectedTaskID: UUID?
+ let isScrollViewFocused: Bool
+ let activeTaskCount: Int
+ let completedTaskCount: Int
+ let selectedIndex: Int?
+ }
+
+ var menuCoordinatorTrigger: MenuState {
+ MenuState(
+ selectedTaskID: selectedTaskID,
+ isScrollViewFocused: focusedField == .scrollView,
+ activeTaskCount: activeTasks.count,
+ completedTaskCount: completedTasks.count,
+ selectedIndex: selectedIndex
+ )
+ }
+
+ func updateMenuCoordinator() {
+ let coord = MenuCoordinator.shared
+ coord.newTask = { createNewTask(); focusedField = nil }
+ coord.deleteSelectedTask = { _ = deleteSelectedTask() }
+ coord.moveSelectedTaskUp = { moveSelectedTaskUp() }
+ coord.moveSelectedTaskDown = { moveSelectedTaskDown() }
+ coord.markSelectedTaskCompleted = { markSelectedTaskCompleted() }
+ coord.clearCompletedTasks = { clearCompletedTasks() }
+ coord.canDeleteSelectedTask = canDeleteSelectionFromList
+ coord.canMoveSelectedTaskUp = canMoveSelectionUp
+ coord.canMoveSelectedTaskDown = canMoveSelectionDown
+ coord.canMarkSelectedTaskCompleted = canMarkSelectionCompleted
+ coord.markCompletedTitle = markCompletedMenuTitle
+ coord.canClearCompletedTasks = !completedTasks.isEmpty
+ }
init(store: TaskStore, syncMonitor: CloudKitSyncMonitor) {
self.store = store
@@ -149,16 +218,20 @@ struct TaskListView: View {
.focusEffectDisabled()
.accessibilityIdentifier("task-list-scrollview")
.keyboardNavigation([
+ ShortcutKey(key: "n", modifiers: .command): createNewTaskFromShortcut,
ShortcutKey(key: .upArrow): navigateUp,
ShortcutKey(key: .downArrow): navigateDown,
- ShortcutKey(key: .space): toggleSelectedTask,
+ ShortcutKey(key: .upArrow, modifiers: .command): moveSelectedTaskUpFromShortcut,
+ ShortcutKey(key: .downArrow, modifiers: .command): moveSelectedTaskDownFromShortcut,
ShortcutKey(key: .return): focusSelectedTask,
+ ShortcutKey(key: .space): toggleSelectedTask,
ShortcutKey(key: .delete): deleteSelectedTask,
])
.onAppear {
if focusedField == nil {
focusedField = .scrollView
}
+ updateMenuCoordinator()
}
.onChange(of: focusedField) { oldValue, newValue in
handleFocusChange(from: oldValue, to: newValue)
@@ -173,7 +246,10 @@ struct TaskListView: View {
focusedField = .scrollView
}
}
+
+ updateMenuCoordinator()
}
+ .onChange(of: menuCoordinatorTrigger) { _, _ in updateMenuCoordinator() }
.onChange(of: undoManager, initial: true) { _, newValue in
managedObjectContext.undoManager = newValue
}
diff --git a/ListlessMac/Views/TaskRowView.swift b/ListlessMac/Views/TaskRowView.swift
@@ -173,11 +173,11 @@ struct TaskRowView: View {
@ViewBuilder
private var selectionBackground: some View {
- if task.isCompleted {
- Color(nsColor: .windowBackgroundColor)
- } else if isSelected {
+ if isSelected {
RoundedRectangle(cornerRadius: 6, style: .continuous)
.fill(Color.accentColor.opacity(0.2))
+ } else if task.isCompleted {
+ Color(nsColor: .windowBackgroundColor)
}
}