← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit 47e27704ad6399b4a21778ae7860d7433cd55ff8
parent d8e196bd917a92ed7a0138dbf02ff92f969cb263
Author: Michael Camilleri <[email protected]>
Date:   Wed, 25 Feb 2026 12:54:46 +0900

Fix miscellaneous issues

Co-Authored-By: Codex GPT 5.3 <[email protected]>

Diffstat

MListlessMac/Extensions/TaskListView+Toolbar.swift | 2+-
MListlessMac/Helpers/ClickableTextField.swift | 2+-
MListlessMac/ListlessMacApp.swift | 65+++++++++++++++++++++++++++++++++++++++++++++--------------------
MListlessMac/Views/TaskListView.swift | 9---------
MListlessiOS/Views/PullToCreate.swift | 5+----

5 files changed, 48 insertions(+), 35 deletions(-)

diff --git a/ListlessMac/Extensions/TaskListView+Toolbar.swift b/ListlessMac/Extensions/TaskListView+Toolbar.swift

@@ -27,7 +27,7 @@ extension TaskListView {
                 } label: {
                     Label("Delete", systemImage: "trash")
                 }
-                .disabled(selectedTaskID == nil)
+                .disabled(selectedTaskID == nil || focusedField != .scrollView)
                 .help("Delete selected task")
 
                 Divider()

diff --git a/ListlessMac/Helpers/ClickableTextField.swift b/ListlessMac/Helpers/ClickableTextField.swift

@@ -7,7 +7,7 @@ class ClickableNSTextField: NSTextField {
 
     override func becomeFirstResponder() -> Bool {
         let result = super.becomeFirstResponder()
-        if result {
+        if result, NSApp.currentEvent?.type == .leftMouseDown {
             onBecomeFirstResponder?()
         }
         return result

diff --git a/ListlessMac/ListlessMacApp.swift b/ListlessMac/ListlessMacApp.swift

@@ -10,9 +10,18 @@ private enum MenuSelectors {
 
 @MainActor
 class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
+    private let persistenceController: PersistenceController
+
+    override init() {
+        let isUITesting = ProcessInfo.processInfo.arguments.contains("UI_TESTING")
+        persistenceController = isUITesting ? PersistenceController(inMemory: true) : .shared
+        super.init()
+    }
+
     func applicationDidFinishLaunching(_ notification: Notification) {
         NSWindow.allowsAutomaticWindowTabbing = false
         installMainMenu()
+        openNewWindow()
     }
 
     // MARK: - NSMenuItemValidation
@@ -22,7 +31,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
     func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
         let coord = MenuCoordinator.shared
         switch menuItem.action {
-        case #selector(handleNewWindow):      return coord.newWindow != nil
+        case #selector(handleNewWindow):      return true
         case #selector(handleDeleteTask):     return coord.canDeleteSelectedTask
         case #selector(handleMoveUp):         return coord.canMoveSelectedTaskUp
         case #selector(handleMoveDown):       return coord.canMoveSelectedTaskDown
@@ -45,7 +54,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
     }
 
     @objc private func handleNewWindow() {
-        MenuCoordinator.shared.newWindow?()
+        openNewWindow()
     }
 
     @objc private func handleMoveUp() {
@@ -64,6 +73,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
         MenuCoordinator.shared.clearCompletedTasks?()
     }
 
+    private func openNewWindow() {
+        let defaultContentSize = NSSize(width: 400, height: 350)
+        let rootView = TaskListView(
+            store: TaskStore(persistenceController: persistenceController),
+            syncMonitor: persistenceController.syncMonitor
+        )
+        .environment(\.managedObjectContext, persistenceController.viewContext)
+
+        let window = NSWindow(
+            contentRect: NSRect(origin: .zero, size: defaultContentSize),
+            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
+            backing: .buffered,
+            defer: false
+        )
+        window.contentViewController = NSHostingController(rootView: rootView)
+        window.setContentSize(defaultContentSize)
+        window.minSize = NSSize(width: 320, height: 240)
+        window.titleVisibility = .hidden
+        window.titlebarAppearsTransparent = true
+        window.isRestorable = false
+        window.center()
+        window.makeKeyAndOrderFront(nil)
+        window.makeFirstResponder(nil)
+        NSApp.activate()
+    }
+
     // MARK: - Main Menu
 
     private func installMainMenu() {
@@ -202,24 +237,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
 }
 
 @main
-struct ListlessMacApp: App {
-    @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
-    private let persistenceController: PersistenceController
-
-    init() {
-        let isUITesting = ProcessInfo.processInfo.arguments.contains("UI_TESTING")
-        persistenceController = isUITesting ? PersistenceController(inMemory: true) : .shared
-    }
-
-    var body: some Scene {
-        WindowGroup(id: "main") {
-            TaskListView(
-                store: TaskStore(persistenceController: persistenceController),
-                syncMonitor: persistenceController.syncMonitor
-            )
-            .environment(\.managedObjectContext, persistenceController.viewContext)
+enum ListlessMacMain {
+    static func main() {
+        let app = NSApplication.shared
+        let delegate = AppDelegate()
+        app.setActivationPolicy(.regular)
+        app.delegate = delegate
+        withExtendedLifetime(delegate) {
+            app.run()
         }
-        .windowStyle(.hiddenTitleBar)
-        .defaultSize(width: 400, height: 350)
     }
 }

diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/TaskListView.swift

@@ -19,7 +19,6 @@ struct TaskListView: View {
 
     struct InteractionStateData {
         var dragState: DragState = .idle
-        var pullOffset: CGFloat = 0
     }
 
     struct TaskStateData {
@@ -28,7 +27,6 @@ struct TaskListView: View {
 
     @Environment(\.undoManager) var undoManager
     @Environment(\.managedObjectContext) var managedObjectContext
-    @Environment(\.openWindow) var openWindow
 
     let store: TaskStore
     @ObservedObject var syncMonitor: CloudKitSyncMonitor
@@ -68,11 +66,6 @@ struct TaskListView: View {
         nonmutating set { iState.dragState = newValue }
     }
 
-    var pullOffset: CGFloat {
-        get { iState.pullOffset }
-        nonmutating set { iState.pullOffset = newValue }
-    }
-
     var refreshID: UUID {
         get { tState.refreshID }
         nonmutating set { tState.refreshID = newValue }
@@ -137,7 +130,6 @@ struct TaskListView: View {
     func updateMenuCoordinator() {
         let coord = MenuCoordinator.shared
         coord.newTask = { createNewTask(); focusedField = nil }
-        coord.newWindow = { openWindow(id: "main") }
         coord.deleteSelectedTask = { _ = deleteSelectedTask() }
         coord.moveSelectedTaskUp = { moveSelectedTaskUp() }
         coord.moveSelectedTaskDown = { moveSelectedTaskDown() }
@@ -253,7 +245,6 @@ struct TaskListView: View {
                 }
             }
             .frame(maxWidth: .infinity, alignment: .topLeading)
-            .offset(y: -pullOffset)
             .dropDestination(for: String.self) { items, location in
                 handleDrop(items: items)
             }

diff --git a/ListlessiOS/Views/PullToCreate.swift b/ListlessiOS/Views/PullToCreate.swift

@@ -9,13 +9,10 @@ struct PullToCreateIndicator: View {
     private let indicatorHeight: CGFloat = 48
     private let textSlideDistance: CGFloat = 22
 
-    // Matches the "top" gradient stop used for the first active task row
-    private let accentColor = Color(hue: 0.98, saturation: 0.85, brightness: 1.0)
-
     var body: some View {
         HStack(spacing: 0) {
             Rectangle()
-                .fill(accentColor)
+                .fill(taskColor(forIndex: 0, total: 1))
                 .frame(width: TaskRowMetrics.accentBarWidth)
             HStack(alignment: .center, spacing: TaskRowMetrics.contentSpacing) {
                 Image(systemName: "circle")