listless

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

commit 7d831d8ddffddb726ec25e3065572a83884564f4
parent 22d14a88e25a2f69ffc59fda81c7c5b4d8532fa7
Author: Michael Camilleri <[email protected]>
Date:   Tue, 17 Feb 2026 16:10:47 +0900

Fix various bugs

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

Diffstat:
MListless.xcodeproj/project.pbxproj | 6++++++
MListless/Models/TaskStore.swift | 4++--
MListless/Sync/PersistenceController.swift | 3+--
AListless/Views/AccentColor.swift | 29+++++++++++++++++++++++++++++
MListless/Views/TaskListView.swift | 26++------------------------
MListlessMac/Views/TaskRowView.swift | 30+++---------------------------
MListlessiOS/Views/TaskRowView.swift | 26+-------------------------
7 files changed, 44 insertions(+), 80 deletions(-)

diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj @@ -11,12 +11,14 @@ 0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; }; 15B71073767FB4766A6BA2BE /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */; }; 1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B048B19C5219862BBED2E7 /* TestHelpers.swift */; }; + 1E3857004DDD888BB4A6ED50 /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0630FFEEFF7662A414E9A8 /* AccentColor.swift */; }; 269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */; }; 3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; }; 3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06485DBE35B60868E14202A /* TaskRowView.swift */; }; 3FCEFB586D9A9085A201AE7D /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BF73E1B8FE337B76D3E757 /* TaskListView+PullToCreate.swift */; }; 42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; }; 5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; }; + 5CFFBC8A17E065640857540A /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0630FFEEFF7662A414E9A8 /* AccentColor.swift */; }; 614FCCA450EC0BFFD8B40640 /* ListlessMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */; }; 6C252050E62AED3A0A684EBF /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; }; 731477635D3F4DFF1F78D673 /* AppColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1FE1858BC9E8915A091D33 /* AppColors.swift */; }; @@ -80,6 +82,7 @@ 74255E6B6C40899E9B17D927 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; }; 75B048B19C5219862BBED2E7 /* TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = "<group>"; }; 7C73E9D4C42CCABBF0F33543 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; }; + 7F0630FFEEFF7662A414E9A8 /* AccentColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentColor.swift; sourceTree = "<group>"; }; 81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; }; 82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; }; 9262207DAC21619BD9EDEE15 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; }; @@ -114,6 +117,7 @@ isa = PBXGroup; children = ( D123BB181208FC825777B0A7 /* .gitkeep */, + 7F0630FFEEFF7662A414E9A8 /* AccentColor.swift */, 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */, 537A913AC421BAEF60D26D9C /* TaskListView.swift */, ); @@ -365,6 +369,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5CFFBC8A17E065640857540A /* AccentColor.swift in Sources */, 83378A0575640417ED41FC25 /* ClickableTextField.swift in Sources */, 15B71073767FB4766A6BA2BE /* HoverCursorModifier.swift in Sources */, D8EFF49E9156083D675D47F0 /* KeyboardNavigationModifier.swift in Sources */, @@ -388,6 +393,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1E3857004DDD888BB4A6ED50 /* AccentColor.swift in Sources */, 731477635D3F4DFF1F78D673 /* AppColors.swift in Sources */, FEC96DAA2BF8BB5D6D8504EB /* HoverCursorModifier.swift in Sources */, 6C252050E62AED3A0A684EBF /* KeyboardNavigationModifier.swift in Sources */, diff --git a/Listless/Models/TaskStore.swift b/Listless/Models/TaskStore.swift @@ -25,9 +25,9 @@ final class TaskStore { let activeTasks = allTasks.filter { !$0.isCompleted } .sorted { $0.sortOrder < $1.sortOrder } - // Completed tasks sorted by updatedAt (most recently completed last) + // Completed tasks sorted by updatedAt (most recently completed first) let completedTasks = allTasks.filter { $0.isCompleted } - .sorted { $0.updatedAt < $1.updatedAt } + .sorted { $0.updatedAt > $1.updatedAt } return activeTasks + completedTasks } catch { diff --git a/Listless/Sync/PersistenceController.swift b/Listless/Sync/PersistenceController.swift @@ -51,8 +51,7 @@ final class PersistenceController { do { try context.save() } catch { - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + print("Failed to save context: \(error.localizedDescription)") } } } diff --git a/Listless/Views/AccentColor.swift b/Listless/Views/AccentColor.swift @@ -0,0 +1,29 @@ +import SwiftUI + +func taskColor(forIndex index: Int, total: Int) -> Color { + guard total > 1 else { return Color(hue: 0.98, saturation: 0.85, brightness: 1.0) } + + // Gradient matches gradient.png: coral/red → pink/magenta → purple/blue + let progress = Double(index) / Double(total - 1) + let top = (h: 0.98, s: 0.85, b: 1.00) + let mid = (h: 0.88, s: 0.75, b: 0.95) + let bottom = (h: 0.72, s: 0.65, b: 0.85) + + if progress < 0.5 { + return interpolateHSB(from: top, to: mid, progress: progress * 2.0) + } else { + return interpolateHSB(from: mid, to: bottom, progress: (progress - 0.5) * 2.0) + } +} + +private func interpolateHSB( + from: (h: Double, s: Double, b: Double), + to: (h: Double, s: Double, b: Double), + progress: Double +) -> Color { + Color( + hue: from.h + (to.h - from.h) * progress, + saturation: from.s + (to.s - from.s) * progress, + brightness: from.b + (to.b - from.b) * progress + ) +} diff --git a/Listless/Views/TaskListView.swift b/Listless/Views/TaskListView.swift @@ -202,7 +202,7 @@ struct TaskListView: View { HStack(spacing: 0) { Rectangle() .fill( - accentColor( + taskColor( forIndex: visualOrder?.firstIndex(of: state.taskID) ?? 0, total: displayActiveTasks.count ) @@ -506,7 +506,7 @@ struct TaskListView: View { } guard let currentID = selectedTaskID else { - selectedTaskID = completedTasks.first?.id ?? activeTasks.first?.id + selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id return .handled } @@ -771,27 +771,5 @@ struct TaskListView: View { visualOrder = nil } - private func accentColor(forIndex index: Int, total: Int) -> Color { - guard total > 1 else { return Color(hue: 0.98, saturation: 0.85, brightness: 1.0) } - let progress = Double(index) / Double(total - 1) - let top = (h: 0.98, s: 0.85, b: 1.00) - let mid = (h: 0.88, s: 0.75, b: 0.95) - let bottom = (h: 0.72, s: 0.65, b: 0.85) - if progress < 0.5 { - let t = progress * 2.0 - return Color( - hue: top.h + (mid.h - top.h) * t, - saturation: top.s + (mid.s - top.s) * t, - brightness: top.b + (mid.b - top.b) * t - ) - } else { - let t = (progress - 0.5) * 2.0 - return Color( - hue: mid.h + (bottom.h - mid.h) * t, - saturation: mid.s + (bottom.s - mid.s) * t, - brightness: mid.b + (bottom.b - mid.b) * t - ) - } - } #endif } diff --git a/ListlessMac/Views/TaskRowView.swift b/ListlessMac/Views/TaskRowView.swift @@ -29,31 +29,7 @@ struct TaskRowView: View { private func computeAccentColor() -> Color { guard !task.isCompleted else { return .clear } - guard totalTasks > 1 else { return Color(hue: 0.98, saturation: 0.85, brightness: 1.0) } - - // Gradient matches gradient.png: coral/red → pink/magenta → purple/blue - let progress = Double(index) / Double(totalTasks - 1) - let top = (h: 0.98, s: 0.85, b: 1.00) - let mid = (h: 0.88, s: 0.75, b: 0.95) - let bottom = (h: 0.72, s: 0.65, b: 0.85) - - if progress < 0.5 { - return interpolateHSB(from: top, to: mid, progress: progress * 2.0) - } else { - return interpolateHSB(from: mid, to: bottom, progress: (progress - 0.5) * 2.0) - } - } - - private func interpolateHSB( - from: (h: Double, s: Double, b: Double), - to: (h: Double, s: Double, b: Double), - progress: Double - ) -> Color { - Color( - hue: from.h + (to.h - from.h) * progress, - saturation: from.s + (to.s - from.s) * progress, - brightness: from.b + (to.b - from.b) * progress - ) + return taskColor(forIndex: index, total: totalTasks) } init( @@ -213,7 +189,7 @@ struct TaskRowView: View { } private func copyToPasteboard() { - let text = isEditing ? editingTitle : task.title + let text = isCurrentlyEditing ? editingTitle : task.title guard !text.isEmpty else { return } let pasteboard = NSPasteboard.general pasteboard.clearContents() @@ -222,7 +198,7 @@ struct TaskRowView: View { private func pasteFromPasteboard() { guard let string = NSPasteboard.general.string(forType: .string) else { return } - if isEditing { + if isCurrentlyEditing { editingTitle = string } onTitleChange(task, string) diff --git a/ListlessiOS/Views/TaskRowView.swift b/ListlessiOS/Views/TaskRowView.swift @@ -164,31 +164,7 @@ struct TaskRowView: View { private func computeAccentColor() -> Color { guard !task.isCompleted else { return .clear } - guard totalTasks > 1 else { return Color(hue: 0.98, saturation: 0.85, brightness: 1.0) } - - // Gradient: coral/red → pink/magenta → purple/blue (matches macOS) - let progress = Double(index) / Double(totalTasks - 1) - let top = (h: 0.98, s: 0.85, b: 1.00) - let mid = (h: 0.88, s: 0.75, b: 0.95) - let bottom = (h: 0.72, s: 0.65, b: 0.85) - - if progress < 0.5 { - return interpolateHSB(from: top, to: mid, progress: progress * 2.0) - } else { - return interpolateHSB(from: mid, to: bottom, progress: (progress - 0.5) * 2.0) - } - } - - private func interpolateHSB( - from: (h: Double, s: Double, b: Double), - to: (h: Double, s: Double, b: Double), - progress: Double - ) -> Color { - Color( - hue: from.h + (to.h - from.h) * progress, - saturation: from.s + (to.s - from.s) * progress, - brightness: from.b + (to.b - from.b) * progress - ) + return taskColor(forIndex: index, total: totalTasks) } @ViewBuilder