listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

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")