← All repositories

listless

A simple list app for Apple platforms

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


commit 6b47c9892ef2fbd01bb52c7208251a1b7558b3e8
parent 6f5fb8048948fbf4a485decac7561b64ef68ea07
Author: Michael Camilleri <[email protected]>
Date:   Wed, 18 Feb 2026 04:15:28 +0900

Commit to separation of platform-specific views

Co-Authored-By: Claude 4.6 Opus <[email protected]>

Diffstat

MListless.xcodeproj/project.pbxproj | 50++++++++++++++++++++++++++------------------------
AListless/Extensions/TaskListView+Logic.swift | 391+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DListless/Views/.gitkeep | 0
DListless/Views/TaskListView.swift | 723-------------------------------------------------------------------------------
DListlessMac/Extensions/TaskListView+NavigationHeader.swift | 7-------
DListlessMac/Extensions/TaskListView+PullToCreate.swift | 7-------
AListlessMac/Views/TaskListView.swift | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AListlessiOS/Extensions/TaskListView+Drag.swift | 44++++++++++++++++++++++++++++++++++++++++++++
AListlessiOS/Views/TaskListView.swift | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

9 files changed, 802 insertions(+), 761 deletions(-)

diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj

@@ -12,14 +12,12 @@
 		172F2DD978988E207610055F /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */; };
 		182D9FB61E3A3650D7D83D2A /* TaskRowSwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067D666DF0AF1C53404ECF7C /* TaskRowSwipeGesture.swift */; };
 		1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B048B19C5219862BBED2E7 /* TestHelpers.swift */; };
-		2374527E3629DD87AE3D2761 /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A741111B4DEB3A7406A95C93 /* TaskListView+PullToCreate.swift */; };
 		269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */; };
 		26F609518DE1055DF09B0159 /* TaskListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */; };
 		2C53717A0EAFA39240615C9A /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57C3CC81C4380EFAE4DB910 /* TaskRowDragGesture.swift */; };
 		322EE74CBED492693B92AD59 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */; };
 		3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
 		3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06485DBE35B60868E14202A /* TaskRowView.swift */; };
-		42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
 		5035EC4C7518A5FF9AD454CA /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F416DD868A4C044F0D64F8D0 /* TaskRowDragGesture.swift */; };
 		5761B201BF46FCA9C5C98CEF /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */; };
 		5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
@@ -30,25 +28,28 @@
 		77FE96F070B1F7FE31A9CE51 /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */; };
 		7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */; };
 		80BCA654FE694D8A77D9BF93 /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */; };
+		82E75475E13FD847DDDC69D8 /* TaskListView+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */; };
 		882695E7AE463C0F39ACFF3C /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */; };
-		8E08F4C82D6F9E67667CB20A /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
 		91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
 		96617677059FABDBB80D642B /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
 		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 */; };
 		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 */; };
-		CAC6A40C680611163775CDFE /* TaskListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E67FE94ED8E9B248A0C3A97 /* TaskListView+NavigationHeader.swift */; };
+		CAD142ED738A83371DFF8F5B /* TaskListView+Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */; };
 		DB5FF6C1AA57D4C9BDDD50FD /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */; };
 		DC73A39A269AB495BCE1AC48 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
 		DEE187A790A4058FE4AFDB2E /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */; };
 		E47136CA7428927395D8C7C7 /* PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */; };
+		E5878BAA0EA66A94440E2B0F /* TaskListView+Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */; };
 		E60F81C7B930AACE67746759 /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7588879D0FA1C2A8BCEF14F /* HoverCursorModifier.swift */; };
 		ECD5E7EA05AE1C00B38C939E /* TaskStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */; };
 		F0B2B806BD84A4F2FDF8E038 /* ListlessiOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */; };
 		F50E8B8F73F64D8E641DC74C /* TaskStoreCompletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */; };
 		F6587B84ECC6BFE92A5FB493 /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */; };
+		FDD09FECEED48EC9598538F4 /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632DA39B24C4CF1528A1A24D /* TaskListView.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -71,15 +72,16 @@
 		2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
 		2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreOrderingTests.swift; sourceTree = "<group>"; };
 		3313FEDB101EECA4B344EEF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
-		3E67FE94ED8E9B248A0C3A97 /* TaskListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+NavigationHeader.swift"; sourceTree = "<group>"; };
+		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>"; };
 		4FC64B9F9370041BEDBD1E14 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
-		537A913AC421BAEF60D26D9C /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
 		5B0E22B8F7B2B7283CAF749E /* Listless macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		5E88DDD119EEEECCD45F36D2 /* TaskStoreCompletionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreCompletionTests.swift; sourceTree = "<group>"; };
+		632DA39B24C4CF1528A1A24D /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
 		6ECE0E961F87BA32FA87BF90 /* TappableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableTextField.swift; sourceTree = "<group>"; };
 		712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+NavigationHeader.swift"; sourceTree = "<group>"; };
+		72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Logic.swift"; sourceTree = "<group>"; };
 		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>"; };
@@ -90,7 +92,6 @@
 		9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreEdgeCaseTests.swift; sourceTree = "<group>"; };
 		9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
 		9FBEB58DD41817F09B0EB9F0 /* Listless.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Listless.xcdatamodel; sourceTree = "<group>"; };
-		A741111B4DEB3A7406A95C93 /* TaskListView+PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToCreate.swift"; sourceTree = "<group>"; };
 		AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessiOSApp.swift; sourceTree = "<group>"; };
 		B7588879D0FA1C2A8BCEF14F /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
 		BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToCreate.swift; sourceTree = "<group>"; };
@@ -99,12 +100,12 @@
 		C9B14DC786A336008AAB78EE /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
 		D10B5491A53E77C80F8F75CD /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
-		D123BB181208FC825777B0A7 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		D57C3CC81C4380EFAE4DB910 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
 		D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickableTextField.swift; sourceTree = "<group>"; };
 		DC3DEE364304587D280C5672 /* TaskStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStore.swift; sourceTree = "<group>"; };
 		E06485DBE35B60868E14202A /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
 		E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
+		E51B8129962E5CC78ECDDC2B /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
 		E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
 		F1E998119283F784B9ADEE28 /* AppColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppColors.swift; sourceTree = "<group>"; };
 		F416DD868A4C044F0D64F8D0 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
@@ -113,15 +114,6 @@
 /* End PBXFileReference section */
 
 /* Begin PBXGroup section */
-		14254B7ADE539045774E2A31 /* Views */ = {
-			isa = PBXGroup;
-			children = (
-				D123BB181208FC825777B0A7 /* .gitkeep */,
-				537A913AC421BAEF60D26D9C /* TaskListView.swift */,
-			);
-			path = Views;
-			sourceTree = "<group>";
-		};
 		19A2FAA566465414CA20E6D8 /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
@@ -161,11 +153,11 @@
 		58051CBDE2390F9E13647235 /* Listless */ = {
 			isa = PBXGroup;
 			children = (
+				A1A9B54C4CBA03BEE12B34A9 /* Extensions */,
 				78E0184C210B140892690CD4 /* Helpers */,
 				AA563E991F69ED14DCD9A1AB /* Infrastructure */,
 				8656EEF3161BE20196B8042E /* Models */,
 				E98EF3B4638A9E8473EA62FA /* Sync */,
-				14254B7ADE539045774E2A31 /* Views */,
 			);
 			path = Listless;
 			sourceTree = "<group>";
@@ -174,6 +166,7 @@
 			isa = PBXGroup;
 			children = (
 				BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */,
+				E51B8129962E5CC78ECDDC2B /* TaskListView.swift */,
 				199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */,
 			);
 			path = Views;
@@ -191,6 +184,7 @@
 		8629C1C94770B3B0D08B580D /* Extensions */ = {
 			isa = PBXGroup;
 			children = (
+				3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */,
 				712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */,
 				9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */,
 				CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */,
@@ -222,6 +216,14 @@
 			path = ListlessiOS;
 			sourceTree = "<group>";
 		};
+		A1A9B54C4CBA03BEE12B34A9 /* Extensions */ = {
+			isa = PBXGroup;
+			children = (
+				72B4668483D05A6ECA142B89 /* TaskListView+Logic.swift */,
+			);
+			path = Extensions;
+			sourceTree = "<group>";
+		};
 		AA563E991F69ED14DCD9A1AB /* Infrastructure */ = {
 			isa = PBXGroup;
 			children = (
@@ -233,6 +235,7 @@
 		D12ECC901ABED96B86CC85B5 /* Views */ = {
 			isa = PBXGroup;
 			children = (
+				632DA39B24C4CF1528A1A24D /* TaskListView.swift */,
 				E06485DBE35B60868E14202A /* TaskRowView.swift */,
 			);
 			path = Views;
@@ -298,8 +301,6 @@
 		F7279CE4B2501F9F5111A3D8 /* Extensions */ = {
 			isa = PBXGroup;
 			children = (
-				3E67FE94ED8E9B248A0C3A97 /* TaskListView+NavigationHeader.swift */,
-				A741111B4DEB3A7406A95C93 /* TaskListView+PullToCreate.swift */,
 				2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */,
 			);
 			path = Extensions;
@@ -419,10 +420,9 @@
 				5761B201BF46FCA9C5C98CEF /* PlatformScrollIndicatorsModifier.swift in Sources */,
 				DEE187A790A4058FE4AFDB2E /* PlatformTextFieldWidthModifier.swift in Sources */,
 				C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */,
-				CAC6A40C680611163775CDFE /* TaskListView+NavigationHeader.swift in Sources */,
-				2374527E3629DD87AE3D2761 /* TaskListView+PullToCreate.swift in Sources */,
+				E5878BAA0EA66A94440E2B0F /* TaskListView+Logic.swift in Sources */,
 				322EE74CBED492693B92AD59 /* TaskListView+Toolbar.swift in Sources */,
-				8E08F4C82D6F9E67667CB20A /* TaskListView.swift in Sources */,
+				FDD09FECEED48EC9598538F4 /* TaskListView.swift in Sources */,
 				5035EC4C7518A5FF9AD454CA /* TaskRowDragGesture.swift in Sources */,
 				3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */,
 				3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */,
@@ -445,10 +445,12 @@
 				E47136CA7428927395D8C7C7 /* PullToCreate.swift in Sources */,
 				0F12D56A528FCBF8A67864CB /* TappableTextField.swift in Sources */,
 				0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */,
+				82E75475E13FD847DDDC69D8 /* TaskListView+Drag.swift in Sources */,
+				CAD142ED738A83371DFF8F5B /* TaskListView+Logic.swift in Sources */,
 				26F609518DE1055DF09B0159 /* TaskListView+NavigationHeader.swift in Sources */,
 				80BCA654FE694D8A77D9BF93 /* TaskListView+PullToCreate.swift in Sources */,
 				77970A2323A598E830D95301 /* TaskListView+Toolbar.swift in Sources */,
-				42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */,
+				C169823665158AA347A63990 /* TaskListView.swift in Sources */,
 				2C53717A0EAFA39240615C9A /* TaskRowDragGesture.swift in Sources */,
 				182D9FB61E3A3650D7D83D2A /* TaskRowSwipeGesture.swift in Sources */,
 				7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */,

diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/TaskListView+Logic.swift

@@ -0,0 +1,391 @@
+import SwiftUI
+
+extension TaskListView {
+
+    // MARK: - Computed Properties
+
+    var activeTasks: [TaskItem] {
+        Array(tasks.filter { !$0.isCompleted })
+            .sorted { $0.sortOrder < $1.sortOrder }
+    }
+
+    var displayActiveTasks: [TaskItem] {
+        guard let visualOrder = visualOrder else {
+            return activeTasks
+        }
+
+        return visualOrder.compactMap { id in
+            activeTasks.first(where: { $0.id == id })
+        }
+    }
+
+    var completedTasks: [TaskItem] {
+        Array(tasks.filter { $0.isCompleted })
+            .sorted { $0.updatedAt > $1.updatedAt }
+    }
+
+    var allTasksInDisplayOrder: [TaskItem] {
+        displayActiveTasks + completedTasks
+    }
+
+    var editingTaskID: UUID? {
+        if case .task(let id) = focusedField {
+            return id
+        }
+        return nil
+    }
+
+    private func isLastActiveTask(_ taskID: UUID) -> Bool {
+        guard let lastTask = activeTasks.last else { return false }
+        return lastTask.id == taskID
+    }
+
+    // MARK: - Task Creation
+
+    func createNewTaskAtTop() {
+        draggedTaskID = nil
+        visualOrder = nil
+        let task = store.createTask(title: "", atBeginning: true)
+        pendingFocus = .task(task.id)
+        focusedField = .task(task.id)
+        selectedTaskID = task.id
+    }
+
+    func createNewTask() {
+        draggedTaskID = nil
+        visualOrder = nil
+        let task = store.createTask(title: "")
+        pendingFocus = .task(task.id)
+        focusedField = .task(task.id)
+        selectedTaskID = task.id
+    }
+
+    // MARK: - Interaction Handlers
+
+    func handleBackgroundTap() {
+        let isTaskFocused = if case .task = focusedField { true } else { false }
+
+        if isTaskFocused || selectedTaskID != nil {
+            selectedTaskID = nil
+            focusedField = nil
+        } else {
+            createNewTask()
+            focusedField = nil
+        }
+    }
+
+    func handleFocusChange(from oldValue: FocusField?, to newValue: FocusField?) {
+        print(
+            "🟣 handleFocusChange() from: \(String(describing: oldValue)) to: \(String(describing: newValue))"
+        )
+        let oldID = taskID(from: oldValue)
+        let newID = taskID(from: newValue)
+
+        guard oldID != newID, let oldID else {
+            print("🟣 handleFocusChange() no action needed")
+            return
+        }
+        print("🟣 handleFocusChange() calling deleteIfEmpty for task \(oldID)")
+        deleteIfEmpty(taskID: oldID)
+    }
+
+    private func taskID(from field: FocusField?) -> UUID? {
+        guard case .task(let id) = field else { return nil }
+        return id
+    }
+
+    private func deleteIfEmpty(taskID: UUID) {
+        if case .task(let pendingTaskID) = pendingFocus, pendingTaskID == taskID {
+            print("🔴 deleteIfEmpty() skipping - task is pending focus")
+            return
+        }
+
+        guard let task = tasks.first(where: { $0.id == taskID }) else {
+            return
+        }
+        let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
+        guard trimmedTitle.isEmpty else { return }
+
+        managedObjectContext.undoManager?.removeAllActions(withTarget: task)
+        managedObjectContext.undoManager?.disableUndoRegistration()
+        deleteTask(task)
+        managedObjectContext.undoManager?.enableUndoRegistration()
+    }
+
+    func updateTitle(_ task: TaskItem, _ title: String) {
+        guard task.title != title else { return }
+        store.updateWithoutSaving(taskID: task.id, title: title)
+    }
+
+    func toggleCompletion(_ task: TaskItem) {
+        if task.isCompleted {
+            store.uncomplete(taskID: task.id)
+        } else {
+            store.complete(taskID: task.id)
+        }
+    }
+
+    func handleSwipeComplete(_ taskID: UUID) {
+        guard let task = tasks.first(where: { $0.id == taskID }) else { return }
+        toggleCompletion(task)
+    }
+
+    func handleSwipeDelete(_ taskID: UUID) {
+        guard let task = tasks.first(where: { $0.id == taskID }) else { return }
+        deleteTask(task)
+    }
+
+    func selectTask(_ taskID: UUID) {
+        selectedTaskID = taskID
+    }
+
+    func deleteTask(_ task: TaskItem) {
+        let taskID = task.id
+        print("🔴 deleteTask() called for task \(taskID)")
+
+        if selectedTaskID == taskID {
+            print("🔴 deleteTask() clearing selectedTaskID")
+            selectedTaskID = nil
+        }
+
+        store.delete(taskID: taskID)
+        print("🔴 deleteTask() completed")
+    }
+
+    func clearCompletedTasks() {
+        for task in completedTasks.reversed() {
+            store.delete(taskID: task.id)
+        }
+    }
+
+    // MARK: - Keyboard Navigation
+
+    func navigateUp() -> KeyPress.Result {
+        print("⬆️ navigateUp() called, focusedField: \(String(describing: focusedField))")
+        guard focusedField == .scrollView else {
+            print("⬆️ navigateUp() IGNORED - focus is not .scrollView")
+            return .ignored
+        }
+
+        guard let currentID = selectedTaskID else {
+            selectedTaskID = activeTasks.last?.id
+            return .handled
+        }
+
+        let displayOrder = allTasksInDisplayOrder
+        guard let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID }) else {
+            return .handled
+        }
+
+        if currentIndex > 0 {
+            selectedTaskID = displayOrder[currentIndex - 1].id
+        }
+        return .handled
+    }
+
+    func navigateDown() -> KeyPress.Result {
+        print("⬇️ navigateDown() called, focusedField: \(String(describing: focusedField))")
+        guard focusedField == .scrollView else {
+            print("⬇️ navigateDown() IGNORED - focus is not .scrollView")
+            return .ignored
+        }
+
+        guard let currentID = selectedTaskID else {
+            selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id
+            return .handled
+        }
+
+        let displayOrder = allTasksInDisplayOrder
+        guard let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID }) else {
+            return .handled
+        }
+
+        if currentIndex < displayOrder.count - 1 {
+            selectedTaskID = displayOrder[currentIndex + 1].id
+        }
+        return .handled
+    }
+
+    func toggleSelectedTask() -> KeyPress.Result {
+        guard focusedField == .scrollView else { return .ignored }
+        guard let currentID = selectedTaskID else { return .handled }
+        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
+            return .handled
+        }
+        toggleCompletion(task)
+        return .handled
+    }
+
+    func focusSelectedTask() -> KeyPress.Result {
+        guard focusedField == .scrollView else { return .ignored }
+        guard let currentID = selectedTaskID else { return .handled }
+        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
+            return .handled
+        }
+        guard !task.isCompleted else { return .handled }
+        startEditing(currentID)
+        return .handled
+    }
+
+    func deleteSelectedTask() -> KeyPress.Result {
+        print("🗑️ deleteSelectedTask() called, focusedField: \(String(describing: focusedField))")
+        guard focusedField == .scrollView else {
+            print("🗑️ deleteSelectedTask() IGNORED - focus is not .scrollView")
+            return .ignored
+        }
+        guard let currentID = selectedTaskID else {
+            print("🗑️ deleteSelectedTask() no task selected")
+            return .handled
+        }
+        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
+            print("🗑️ deleteSelectedTask() task not found")
+            return .handled
+        }
+        print("🗑️ deleteSelectedTask() deleting task \(currentID)")
+        deleteTask(task)
+        return .handled
+    }
+
+    // MARK: - Focus Management
+
+    func focusTextField(_ taskID: UUID) {
+        focusedField = .task(taskID)
+    }
+
+    func startEditing(_ taskID: UUID) {
+        print("🟢 startEditing called for task \(taskID)")
+        selectedTaskID = taskID
+        focusedField = .task(taskID)
+        pendingFocus = nil
+        print("🟢 startEditing set focusedField = .task(\(taskID))")
+    }
+
+    func endEditing(_ taskID: UUID, shouldCreateNewTask: Bool) {
+        print(
+            "🟢 endEditing() called for task \(taskID), shouldCreateNewTask: \(shouldCreateNewTask)"
+        )
+        store.save()
+
+        let wasLastActiveTask = isLastActiveTask(taskID)
+        let willBeDeleted = shouldDeleteIfEmpty(taskID: taskID)
+        print(
+            "🟢 endEditing() wasLastActiveTask: \(wasLastActiveTask), willBeDeleted: \(willBeDeleted)"
+        )
+
+        if willBeDeleted {
+            print("🟢 endEditing() deleting task - focus will be repaired automatically by onChange")
+            selectedTaskID = nil
+            deleteIfEmpty(taskID: taskID)
+        } else if wasLastActiveTask && shouldCreateNewTask {
+            print("🟢 endEditing() creating new task")
+            createNewTask()
+        } else if shouldCreateNewTask {
+            print("🟢 endEditing() Return on non-last task — dismiss keyboard, enter navigation mode")
+            focusedField = .scrollView
+        } else {
+            print("🟢 endEditing() done, selection unchanged")
+        }
+
+        print("🟢 endEditing() completed, final focus: \(String(describing: focusedField))")
+    }
+
+    private func shouldDeleteIfEmpty(taskID: UUID) -> Bool {
+        guard let task = tasks.first(where: { $0.id == taskID }) else {
+            return false
+        }
+        let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
+        return trimmedTitle.isEmpty
+    }
+
+    // MARK: - Drag and Drop
+
+    func startDrag(taskID: UUID) {
+        guard draggedTaskID == nil else { return }
+        draggedTaskID = taskID
+        visualOrder = activeTasks.map(\.id)
+        didStartDrag()
+    }
+
+    func updateVisualOrder(insertBefore targetID: UUID) {
+        guard let draggedID = draggedTaskID,
+            let order = visualOrder
+        else { return }
+
+        var newOrder = order.filter { $0 != draggedID }
+        if let targetIndex = newOrder.firstIndex(of: targetID) {
+            newOrder.insert(draggedID, at: targetIndex)
+        }
+
+        if newOrder != visualOrder {
+            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
+                visualOrder = newOrder
+            }
+        }
+    }
+
+    func updateVisualOrder(insertAfter targetID: UUID) {
+        guard let draggedID = draggedTaskID,
+            let order = visualOrder
+        else { return }
+
+        var newOrder = order.filter { $0 != draggedID }
+        if let targetIndex = newOrder.firstIndex(of: targetID) {
+            newOrder.insert(draggedID, at: targetIndex + 1)
+        }
+
+        if newOrder != visualOrder {
+            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
+                visualOrder = newOrder
+            }
+        }
+    }
+
+    func updateVisualOrderSmart(relativeTo targetID: UUID) {
+        guard let draggedID = draggedTaskID,
+            let order = visualOrder
+        else { return }
+
+        guard let draggedIndex = order.firstIndex(of: draggedID),
+            let targetIndex = order.firstIndex(of: targetID)
+        else { return }
+
+        if draggedIndex < targetIndex {
+            updateVisualOrder(insertAfter: targetID)
+        } else {
+            updateVisualOrder(insertBefore: targetID)
+        }
+    }
+
+    func updateVisualOrder(insertAtEnd: Bool) {
+        guard let draggedID = draggedTaskID,
+            let order = visualOrder
+        else { return }
+
+        var newOrder = order.filter { $0 != draggedID }
+        newOrder.append(draggedID)
+
+        if newOrder != visualOrder {
+            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
+                visualOrder = newOrder
+            }
+        }
+    }
+
+    func handleDrop(items: [String]) -> Bool {
+        guard let droppedUUIDString = items.first,
+            let droppedUUID = UUID(uuidString: droppedUUIDString),
+            let order = visualOrder,
+            let finalIndex = order.firstIndex(of: droppedUUID)
+        else {
+            draggedTaskID = nil
+            visualOrder = nil
+            return false
+        }
+
+        store.moveTask(taskID: droppedUUID, toIndex: finalIndex)
+        draggedTaskID = nil
+        visualOrder = nil
+
+        return true
+    }
+}

diff --git a/Listless/Views/.gitkeep b/Listless/Views/.gitkeep

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

@@ -1,723 +0,0 @@
-import SwiftUI
-
-struct TaskListView: View {
-    enum FocusField: Hashable {
-        case task(UUID)
-        case scrollView
-    }
-
-    @Environment(\.undoManager) private var undoManager
-    @Environment(\.managedObjectContext) private var managedObjectContext
-
-    @State private var store: TaskStore
-    @FetchRequest(
-        sortDescriptors: [
-            NSSortDescriptor(keyPath: \TaskItem.isCompleted, ascending: true),
-            NSSortDescriptor(keyPath: \TaskItem.createdAt, ascending: true),
-        ],
-        animation: .default
-    )
-    private var tasks: FetchedResults<TaskItem>
-    @FocusState var focusedField: FocusField?
-    @State var selectedTaskID: UUID?
-    @State private var refreshID = UUID()
-    @State private var draggedTaskID: UUID?
-    @State private var visualOrder: [UUID]?
-    @State private var pendingFocus: FocusField?
-    @State var pullOffset: CGFloat = 0
-    #if os(iOS)
-    @State private var isDragging: Bool = false
-    @State private var rowFrames: [UUID: CGRect] = [:]
-    #endif
-
-    init(store: TaskStore = TaskStore()) {
-        _store = State(wrappedValue: store)
-    }
-
-    var body: some View {
-        ScrollView {
-            VStack(alignment: .leading, spacing: vStackSpacing) {
-                navigationHeader
-                pullToCreateIndicatorRow
-                ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in
-                    let taskID = task.id
-                    #if os(iOS)
-                    TaskRowView(
-                        task: task,
-                        taskID: taskID,
-                        index: index,
-                        totalTasks: displayActiveTasks.count,
-                        isSelected: selectedTaskID == taskID,
-                        isDragging: $isDragging,
-                        focusedField: $focusedField,
-                        onToggle: { toggleCompletion($0) },
-                        onTitleChange: { updateTitle($0, $1) },
-                        onDelete: { deleteTask($0) },
-                        onSelect: { selectTask($0) },
-                        onStartEdit: { startEditing($0) },
-                        onEndEdit: { endEditing($0, shouldCreateNewTask: $1) }
-                    )
-                    .scaleEffect(draggedTaskID == taskID ? 1.05 : 1.0)
-                    .shadow(
-                        color: draggedTaskID == taskID ? .black.opacity(0.3) : .clear,
-                        radius: 12, y: 4
-                    )
-                    .zIndex(draggedTaskID == taskID ? 1 : 0)
-                    .taskDragGesture(
-                        isActive: !task.isCompleted,
-                        taskID: taskID,
-                        onDragStart: { startDrag(taskID: taskID) },
-                        onDragChanged: { point in handleIOSDragChanged(taskID: taskID, point: point) },
-                        onDragEnded: { commitIOSDrag() }
-                    )
-                    .onGeometryChange(for: CGRect.self) { proxy in
-                        proxy.frame(in: .global)
-                    } action: { frame in
-                        rowFrames[taskID] = frame
-                    }
-                    #else
-                    TaskRowView(
-                        task: task,
-                        taskID: taskID,
-                        index: index,
-                        totalTasks: displayActiveTasks.count,
-                        isSelected: selectedTaskID == taskID,
-                        focusedField: $focusedField,
-                        onToggle: { toggleCompletion($0) },
-                        onTitleChange: { updateTitle($0, $1) },
-                        onDelete: { deleteTask($0) },
-                        onSelect: { selectTask($0) },
-                        onStartEdit: { startEditing($0) },
-                        onEndEdit: { endEditing($0, shouldCreateNewTask: $1) }
-                    )
-                    .taskDragGesture(
-                        isActive: !task.isCompleted,
-                        taskID: task.id,
-                        onDragStart: { startDrag(taskID: task.id) }
-                    )
-                    .overlay {
-                        if draggedTaskID != nil && draggedTaskID != task.id {
-                            VStack(spacing: 0) {
-                                // Top 1/6 - insert BEFORE
-                                Color.clear
-                                    .frame(maxHeight: .infinity)
-                                    .layoutPriority(1)
-                                    .dropDestination(
-                                        for: String.self, action: { _, _ in false },
-                                        isTargeted: { isTargeted in
-                                            if isTargeted {
-                                                updateVisualOrder(insertBefore: task.id)
-                                            }
-                                        })
-
-                                // Middle 2/3 - insert based on direction
-                                Color.clear
-                                    .frame(maxHeight: .infinity)
-                                    .layoutPriority(4)
-                                    .dropDestination(
-                                        for: String.self, action: { _, _ in false },
-                                        isTargeted: { isTargeted in
-                                            if isTargeted {
-                                                updateVisualOrderSmart(relativeTo: task.id)
-                                            }
-                                        })
-
-                                // Bottom 1/6 - insert AFTER
-                                Color.clear
-                                    .frame(maxHeight: .infinity)
-                                    .layoutPriority(1)
-                                    .dropDestination(
-                                        for: String.self, action: { _, _ in false },
-                                        isTargeted: { isTargeted in
-                                            if isTargeted {
-                                                updateVisualOrder(insertAfter: task.id)
-                                            }
-                                        })
-                            }
-                        }
-                    }
-                    #endif
-                }
-
-                #if os(macOS)
-                // Drop zone at the end
-                if !activeTasks.isEmpty && draggedTaskID != nil {
-                    Color.clear
-                        .frame(height: 44)
-                        .dropDestination(
-                            for: String.self, action: { _, _ in false },
-                            isTargeted: { isTargeted in
-                                if isTargeted {
-                                    updateVisualOrder(insertAtEnd: true)
-                                }
-                            })
-                }
-                #endif
-
-                ForEach(completedTasks) { task in
-                    let taskID = task.id
-                    TaskRowView(
-                        task: task,
-                        taskID: taskID,
-                        isSelected: selectedTaskID == taskID,
-                        focusedField: $focusedField,
-                        onToggle: { toggleCompletion($0) },
-                        onTitleChange: { updateTitle($0, $1) },
-                        onDelete: { deleteTask($0) },
-                        onSelect: { selectTask($0) }
-                    )
-                }
-            }
-            .frame(maxWidth: .infinity, alignment: .topLeading)
-            #if os(iOS)
-            .padding(.trailing, 16)
-            .padding(.vertical, 12)
-            #endif
-            .offset(y: -pullOffset)
-            #if os(macOS)
-            .dropDestination(for: String.self) { items, location in
-                handleDrop(items: items)
-            }
-            #endif
-        }
-        #if os(iOS)
-        .scrollDisabled(draggedTaskID != nil)
-        .background {
-            Color.outerBackground.ignoresSafeArea()
-        }
-        .onScrollGeometryChange(for: CGFloat.self) { geo in
-            max(0, -(geo.contentOffset.y + geo.contentInsets.top))
-        } action: { _, pullDistance in
-            pullOffset = pullDistance
-        }
-        .onScrollPhaseChange { oldPhase, newPhase in
-            if oldPhase == .interacting && newPhase != .interacting {
-                if pullOffset >= pullCreateThreshold { createNewTaskAtTop() }
-                pullOffset = 0
-            }
-        }
-        .sensoryFeedback(.impact(weight: .medium), trigger: pullOffset >= pullCreateThreshold) { old, new in
-            !old && new
-        }
-        #endif
-        .contentShape(Rectangle())
-        .onTapGesture {
-            handleBackgroundTap()
-        }
-        .focusable()
-        .focused($focusedField, equals: .scrollView)
-        .focusEffectDisabled()
-        .accessibilityIdentifier("task-list-scrollview")
-        .keyboardNavigation([
-            ShortcutKey(key: .upArrow): navigateUp,
-            ShortcutKey(key: .downArrow): navigateDown,
-            ShortcutKey(key: .space): toggleSelectedTask,
-            ShortcutKey(key: .return): focusSelectedTask,
-            ShortcutKey(key: .delete): deleteSelectedTask,
-        ])
-        .onAppear {
-            // Set initial focus to enable keyboard navigation
-            if focusedField == nil {
-                focusedField = .scrollView
-            }
-        }
-        .onChange(of: focusedField) { oldValue, newValue in
-            handleFocusChange(from: oldValue, to: newValue)
-
-            // Focus repair/resolution: when focus becomes nil, either resolve pending focus or repair to scrollView
-            if newValue == nil {
-                if let pending = pendingFocus {
-                    print("🟣 onChange resolving pendingFocus: \(pending)")
-                    focusedField = pending
-                    pendingFocus = nil
-                } else {
-                    print("🟣 onChange repairing nil focus to .scrollView")
-                    focusedField = .scrollView
-                }
-            }
-        }
-        .onChange(of: undoManager, initial: true) { _, newValue in
-            // Connect SwiftUI's undo manager to Core Data context for automatic undo/redo
-            managedObjectContext.undoManager = newValue
-        }
-        .toolbar {
-            platformToolbar
-        }
-    }
-
-    private var vStackSpacing: CGFloat {
-        #if os(iOS)
-        12
-        #else
-        0
-        #endif
-    }
-
-    private var activeTasks: [TaskItem] {
-        Array(tasks.filter { !$0.isCompleted })
-            .sorted { $0.sortOrder < $1.sortOrder }
-    }
-
-    private var displayActiveTasks: [TaskItem] {
-        guard let visualOrder = visualOrder else {
-            return activeTasks
-        }
-
-        return visualOrder.compactMap { id in
-            activeTasks.first(where: { $0.id == id })
-        }
-    }
-
-    var completedTasks: [TaskItem] {
-        Array(tasks.filter { $0.isCompleted })
-            .sorted { $0.updatedAt > $1.updatedAt }
-    }
-
-    var allTasksInDisplayOrder: [TaskItem] {
-        displayActiveTasks + completedTasks
-    }
-
-    private var editingTaskID: UUID? {
-        if case .task(let id) = focusedField {
-            return id
-        }
-        return nil
-    }
-
-    private func isLastActiveTask(_ taskID: UUID) -> Bool {
-        guard let lastTask = activeTasks.last else { return false }
-        return lastTask.id == taskID
-    }
-
-    private func createNewTaskAtTop() {
-        draggedTaskID = nil
-        visualOrder = nil
-        let task = store.createTask(title: "", atBeginning: true)
-        pendingFocus = .task(task.id)
-        focusedField = .task(task.id)
-        selectedTaskID = task.id
-    }
-
-    func createNewTask() {
-        // Clear any lingering drag state
-        draggedTaskID = nil
-        visualOrder = nil
-
-        // Create Core Data task (Core Data assigns the ID)
-        let task = store.createTask(title: "")
-
-        // Record intent to focus the new task.
-        // pendingFocus is retained for the background-tap flow (focusedField → nil there).
-        // focusedField is also set directly for the TappableTextField Return flow (stays non-nil).
-        pendingFocus = .task(task.id)
-        focusedField = .task(task.id)
-        selectedTaskID = task.id
-    }
-
-
-    private func handleBackgroundTap() {
-        // Check if a task is focused (not just scrollView)
-        let isTaskFocused = if case .task = focusedField { true } else { false }
-
-        if isTaskFocused || selectedTaskID != nil {
-            selectedTaskID = nil
-            focusedField = nil
-        } else {
-            createNewTask()
-            // Trigger focus resolution by setting to nil
-            // onChange(of: focusedField) will then resolve pendingFocus
-            focusedField = nil
-        }
-    }
-
-    private func handleFocusChange(from oldValue: FocusField?, to newValue: FocusField?) {
-        print(
-            "🟣 handleFocusChange() from: \(String(describing: oldValue)) to: \(String(describing: newValue))"
-        )
-        let oldID = taskID(from: oldValue)
-        let newID = taskID(from: newValue)
-
-        guard oldID != newID, let oldID else {
-            print("🟣 handleFocusChange() no action needed")
-            return
-        }
-        print("🟣 handleFocusChange() calling deleteIfEmpty for task \(oldID)")
-        deleteIfEmpty(taskID: oldID)
-    }
-
-    private func taskID(from field: FocusField?) -> UUID? {
-        guard case .task(let id) = field else { return nil }
-        return id
-    }
-
-    private func deleteIfEmpty(taskID: UUID) {
-        // Don't delete if this is the pending focus target
-        if case .task(let pendingTaskID) = pendingFocus, pendingTaskID == taskID {
-            print("🔴 deleteIfEmpty() skipping - task is pending focus")
-            return
-        }
-
-        guard let task = tasks.first(where: { $0.id == taskID }) else {
-            return
-        }
-        let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
-        guard trimmedTitle.isEmpty else { return }
-
-        // Remove this task from undo history since it was never really used
-        managedObjectContext.undoManager?.removeAllActions(withTarget: task)
-
-        // Disable undo registration for the delete operation itself
-        managedObjectContext.undoManager?.disableUndoRegistration()
-        deleteTask(task)
-        managedObjectContext.undoManager?.enableUndoRegistration()
-    }
-
-    private func updateTitle(_ task: TaskItem, _ title: String) {
-        guard task.title != title else { return }
-        store.updateWithoutSaving(taskID: task.id, title: title)
-    }
-
-    private func toggleCompletion(_ task: TaskItem) {
-        if task.isCompleted {
-            store.uncomplete(taskID: task.id)
-        } else {
-            store.complete(taskID: task.id)
-        }
-    }
-
-    private func handleSwipeComplete(_ taskID: UUID) {
-        guard let task = tasks.first(where: { $0.id == taskID }) else { return }
-        toggleCompletion(task)
-    }
-
-    private func handleSwipeDelete(_ taskID: UUID) {
-        guard let task = tasks.first(where: { $0.id == taskID }) else { return }
-        deleteTask(task)
-    }
-
-    private func selectTask(_ taskID: UUID) {
-        selectedTaskID = taskID
-    }
-
-    func deleteTask(_ task: TaskItem) {
-        let taskID = task.id
-        print("🔴 deleteTask() called for task \(taskID)")
-
-        // Clear selection if this task was selected
-        if selectedTaskID == taskID {
-            print("🔴 deleteTask() clearing selectedTaskID")
-            selectedTaskID = nil
-        }
-
-        store.delete(taskID: taskID)
-        print("🔴 deleteTask() completed")
-    }
-
-    func clearCompletedTasks() {
-        // Delete all completed tasks (in reverse to avoid index issues)
-        for task in completedTasks.reversed() {
-            store.delete(taskID: task.id)
-        }
-    }
-
-    private func navigateUp() -> KeyPress.Result {
-        print("⬆️ navigateUp() called, focusedField: \(String(describing: focusedField))")
-        guard focusedField == .scrollView else {
-            print("⬆️ navigateUp() IGNORED - focus is not .scrollView")
-            return .ignored
-        }
-
-        guard let currentID = selectedTaskID else {
-            selectedTaskID = activeTasks.last?.id
-            return .handled
-        }
-
-        let displayOrder = allTasksInDisplayOrder
-        guard let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID }) else {
-            return .handled
-        }
-
-        if currentIndex > 0 {
-            selectedTaskID = displayOrder[currentIndex - 1].id
-        }
-        return .handled
-    }
-
-    private func navigateDown() -> KeyPress.Result {
-        print("⬇️ navigateDown() called, focusedField: \(String(describing: focusedField))")
-        guard focusedField == .scrollView else {
-            print("⬇️ navigateDown() IGNORED - focus is not .scrollView")
-            return .ignored
-        }
-
-        guard let currentID = selectedTaskID else {
-            selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id
-            return .handled
-        }
-
-        let displayOrder = allTasksInDisplayOrder
-        guard let currentIndex = displayOrder.firstIndex(where: { $0.id == currentID }) else {
-            return .handled
-        }
-
-        if currentIndex < displayOrder.count - 1 {
-            selectedTaskID = displayOrder[currentIndex + 1].id
-        }
-        return .handled
-    }
-
-    private func toggleSelectedTask() -> KeyPress.Result {
-        guard focusedField == .scrollView else { return .ignored }
-        guard let currentID = selectedTaskID else { return .handled }
-        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
-            return .handled
-        }
-        toggleCompletion(task)
-        return .handled
-    }
-
-    private func focusSelectedTask() -> KeyPress.Result {
-        guard focusedField == .scrollView else { return .ignored }
-        guard let currentID = selectedTaskID else { return .handled }
-        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
-            return .handled
-        }
-        guard !task.isCompleted else { return .handled }
-        startEditing(currentID)
-        return .handled
-    }
-
-    private func deleteSelectedTask() -> KeyPress.Result {
-        print("🗑️ deleteSelectedTask() called, focusedField: \(String(describing: focusedField))")
-        guard focusedField == .scrollView else {
-            print("🗑️ deleteSelectedTask() IGNORED - focus is not .scrollView")
-            return .ignored
-        }
-        guard let currentID = selectedTaskID else {
-            print("🗑️ deleteSelectedTask() no task selected")
-            return .handled
-        }
-        guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else {
-            print("🗑️ deleteSelectedTask() task not found")
-            return .handled
-        }
-        print("🗑️ deleteSelectedTask() deleting task \(currentID)")
-        deleteTask(task)
-        return .handled
-    }
-
-    // MARK: - Focus Management
-
-    private func focusTextField(_ taskID: UUID) {
-        focusedField = .task(taskID)
-    }
-
-    private func startEditing(_ taskID: UUID) {
-        print("🟢 startEditing called for task \(taskID)")
-        selectedTaskID = taskID
-        focusedField = .task(taskID)
-        pendingFocus = nil    // Consume pendingFocus once the field is live
-        print("🟢 startEditing set focusedField = .task(\(taskID))")
-    }
-
-    private func endEditing(_ taskID: UUID, shouldCreateNewTask: Bool) {
-        print(
-            "🟢 endEditing() called for task \(taskID), shouldCreateNewTask: \(shouldCreateNewTask)")
-        // Save any pending changes
-        store.save()
-
-        // Check conditions BEFORE deleting the task
-        let wasLastActiveTask = isLastActiveTask(taskID)
-        let willBeDeleted = shouldDeleteIfEmpty(taskID: taskID)
-        print(
-            "🟢 endEditing() wasLastActiveTask: \(wasLastActiveTask), willBeDeleted: \(willBeDeleted)"
-        )
-
-        if willBeDeleted {
-            print("🟢 endEditing() deleting task - focus will be repaired automatically by onChange")
-            selectedTaskID = nil
-            deleteIfEmpty(taskID: taskID)
-            // No explicit focus management - onChange will repair to .scrollView
-        } else if wasLastActiveTask && shouldCreateNewTask {
-            print("🟢 endEditing() creating new task")
-            createNewTask()
-        } else if shouldCreateNewTask {
-            print("🟢 endEditing() Return on non-last task — dismiss keyboard, enter navigation mode")
-            // TappableTextField returns false from textFieldShouldReturn, so the old field
-            // stays first responder. Setting focusedField to .scrollView causes SwiftUI's
-            // .focused() to detect the mismatch and call resignFirstResponder(), dismissing
-            // the keyboard cleanly.
-            focusedField = .scrollView
-        } else {
-            print("🟢 endEditing() done, selection unchanged")
-            // Do NOT restore selectedTaskID = taskID here.
-            //
-            // On macOS: selectedTaskID is already taskID (set by startEditing when editing
-            // began), and AppKit fires controlTextDidEndEditing synchronously before any
-            // SwiftUI tap gesture handler runs, so nothing has changed it yet. The line
-            // would be a no-op.
-            //
-            // On iOS: everything flows through onChange(of: focusedField), so onStartEdit
-            // on the new row may have already updated selectedTaskID before this fires.
-            // Restoring it here would overwrite the new selection.
-        }
-
-        print("🟢 endEditing() completed, final focus: \(String(describing: focusedField))")
-    }
-
-    private func shouldDeleteIfEmpty(taskID: UUID) -> Bool {
-        guard let task = tasks.first(where: { $0.id == taskID }) else {
-            return false
-        }
-        let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
-        return trimmedTitle.isEmpty
-    }
-
-    // MARK: - Drag and Drop
-
-    private func startDrag(taskID: UUID) {
-        guard draggedTaskID == nil else { return }
-        draggedTaskID = taskID
-        visualOrder = activeTasks.map(\.id)
-        #if os(iOS)
-        isDragging = true
-        let generator = UIImpactFeedbackGenerator(style: .medium)
-        generator.impactOccurred()
-        #endif
-    }
-
-    private func updateVisualOrder(insertBefore targetID: UUID) {
-        guard let draggedID = draggedTaskID,
-            let order = visualOrder
-        else { return }
-
-        var newOrder = order.filter { $0 != draggedID }
-        if let targetIndex = newOrder.firstIndex(of: targetID) {
-            newOrder.insert(draggedID, at: targetIndex)
-        }
-
-        if newOrder != visualOrder {
-            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
-                visualOrder = newOrder
-            }
-        }
-    }
-
-    private func updateVisualOrder(insertAfter targetID: UUID) {
-        guard let draggedID = draggedTaskID,
-            let order = visualOrder
-        else { return }
-
-        var newOrder = order.filter { $0 != draggedID }
-        if let targetIndex = newOrder.firstIndex(of: targetID) {
-            newOrder.insert(draggedID, at: targetIndex + 1)
-        }
-
-        if newOrder != visualOrder {
-            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
-                visualOrder = newOrder
-            }
-        }
-    }
-
-    private func updateVisualOrderSmart(relativeTo targetID: UUID) {
-        guard let draggedID = draggedTaskID,
-            let order = visualOrder
-        else { return }
-
-        // Determine if dragged item is currently above or below target
-        guard let draggedIndex = order.firstIndex(of: draggedID),
-            let targetIndex = order.firstIndex(of: targetID)
-        else { return }
-
-        if draggedIndex < targetIndex {
-            // Dragging from above → insert after target
-            updateVisualOrder(insertAfter: targetID)
-        } else {
-            // Dragging from below → insert before target
-            updateVisualOrder(insertBefore: targetID)
-        }
-    }
-
-    private func updateVisualOrder(insertAtEnd: Bool) {
-        guard let draggedID = draggedTaskID,
-            let order = visualOrder
-        else { return }
-
-        var newOrder = order.filter { $0 != draggedID }
-        newOrder.append(draggedID)
-
-        if newOrder != visualOrder {
-            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
-                visualOrder = newOrder
-            }
-        }
-    }
-
-    private func handleDrop(items: [String]) -> Bool {
-        guard let droppedUUIDString = items.first,
-            let droppedUUID = UUID(uuidString: droppedUUIDString),
-            let order = visualOrder,
-            let finalIndex = order.firstIndex(of: droppedUUID)
-        else {
-            draggedTaskID = nil
-            visualOrder = nil
-            return false
-        }
-
-        // Commit the reorder
-        store.moveTask(taskID: droppedUUID, toIndex: finalIndex)
-
-        // Clear drag state
-        draggedTaskID = nil
-        visualOrder = nil
-
-        return true
-    }
-
-    // MARK: - iOS Drag Helpers
-
-    #if os(iOS)
-    private func handleIOSDragChanged(taskID: UUID, point: CGPoint) {
-        guard let draggedID = draggedTaskID,
-              var order = visualOrder,
-              let currentIndex = order.firstIndex(of: draggedID),
-              let draggedFrame = rowFrames[draggedID] else { return }
-
-        let threshold = draggedFrame.height * 0.2
-
-        // Swap down: finger moved past the bottom edge of the dragged row
-        if currentIndex < order.count - 1 && point.y > draggedFrame.maxY + threshold {
-            order.swapAt(currentIndex, currentIndex + 1)
-            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
-                visualOrder = order
-            }
-            return
-        }
-
-        // Swap up: finger moved past the top edge of the dragged row
-        if currentIndex > 0 && point.y < draggedFrame.minY - threshold {
-            order.swapAt(currentIndex, currentIndex - 1)
-            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
-                visualOrder = order
-            }
-        }
-    }
-
-    private func commitIOSDrag() {
-        guard let draggedID = draggedTaskID,
-              let order = visualOrder,
-              let finalIndex = order.firstIndex(of: draggedID) else {
-            draggedTaskID = nil
-            visualOrder = nil
-            isDragging = false
-            return
-        }
-        store.moveTask(taskID: draggedID, toIndex: finalIndex)
-        draggedTaskID = nil
-        visualOrder = nil
-        isDragging = false
-    }
-
-    #endif
-}

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

@@ -1,7 +0,0 @@
-import SwiftUI
-
-extension TaskListView {
-    var navigationHeader: some View {
-        EmptyView()
-    }
-}

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

@@ -1,7 +0,0 @@
-import SwiftUI
-
-extension TaskListView {
-    var pullToCreateIndicatorRow: some View {
-        EmptyView()
-    }
-}

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

@@ -0,0 +1,178 @@
+import SwiftUI
+
+struct TaskListView: View {
+    enum FocusField: Hashable {
+        case task(UUID)
+        case scrollView
+    }
+
+    @Environment(\.undoManager) var undoManager
+    @Environment(\.managedObjectContext) var managedObjectContext
+
+    @State var store: TaskStore
+    @FetchRequest(
+        sortDescriptors: [
+            NSSortDescriptor(keyPath: \TaskItem.isCompleted, ascending: true),
+            NSSortDescriptor(keyPath: \TaskItem.createdAt, ascending: true),
+        ],
+        animation: .default
+    )
+    var tasks: FetchedResults<TaskItem>
+    @FocusState var focusedField: FocusField?
+    @State var selectedTaskID: UUID?
+    @State private var refreshID = UUID()
+    @State var draggedTaskID: UUID?
+    @State var visualOrder: [UUID]?
+    @State var pendingFocus: FocusField?
+    @State var pullOffset: CGFloat = 0
+
+    var vStackSpacing: CGFloat { 0 }
+
+    init(store: TaskStore = TaskStore()) {
+        _store = State(wrappedValue: store)
+    }
+
+    func didStartDrag() {}
+
+    var body: some View {
+        ScrollView {
+            VStack(alignment: .leading, spacing: vStackSpacing) {
+                ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in
+                    let taskID = task.id
+                    TaskRowView(
+                        task: task,
+                        taskID: taskID,
+                        index: index,
+                        totalTasks: displayActiveTasks.count,
+                        isSelected: selectedTaskID == taskID,
+                        focusedField: $focusedField,
+                        onToggle: { toggleCompletion($0) },
+                        onTitleChange: { updateTitle($0, $1) },
+                        onDelete: { deleteTask($0) },
+                        onSelect: { selectTask($0) },
+                        onStartEdit: { startEditing($0) },
+                        onEndEdit: { endEditing($0, shouldCreateNewTask: $1) }
+                    )
+                    .taskDragGesture(
+                        isActive: !task.isCompleted,
+                        taskID: task.id,
+                        onDragStart: { startDrag(taskID: task.id) }
+                    )
+                    .overlay {
+                        if draggedTaskID != nil && draggedTaskID != task.id {
+                            VStack(spacing: 0) {
+                                // Top 1/6 - insert BEFORE
+                                Color.clear
+                                    .frame(maxHeight: .infinity)
+                                    .layoutPriority(1)
+                                    .dropDestination(
+                                        for: String.self, action: { _, _ in false },
+                                        isTargeted: { isTargeted in
+                                            if isTargeted {
+                                                updateVisualOrder(insertBefore: task.id)
+                                            }
+                                        })
+
+                                // Middle 2/3 - insert based on direction
+                                Color.clear
+                                    .frame(maxHeight: .infinity)
+                                    .layoutPriority(4)
+                                    .dropDestination(
+                                        for: String.self, action: { _, _ in false },
+                                        isTargeted: { isTargeted in
+                                            if isTargeted {
+                                                updateVisualOrderSmart(relativeTo: task.id)
+                                            }
+                                        })
+
+                                // Bottom 1/6 - insert AFTER
+                                Color.clear
+                                    .frame(maxHeight: .infinity)
+                                    .layoutPriority(1)
+                                    .dropDestination(
+                                        for: String.self, action: { _, _ in false },
+                                        isTargeted: { isTargeted in
+                                            if isTargeted {
+                                                updateVisualOrder(insertAfter: task.id)
+                                            }
+                                        })
+                            }
+                        }
+                    }
+                }
+
+                // Drop zone at the end
+                if !activeTasks.isEmpty && draggedTaskID != nil {
+                    Color.clear
+                        .frame(height: 44)
+                        .dropDestination(
+                            for: String.self, action: { _, _ in false },
+                            isTargeted: { isTargeted in
+                                if isTargeted {
+                                    updateVisualOrder(insertAtEnd: true)
+                                }
+                            })
+                }
+
+                ForEach(completedTasks) { task in
+                    let taskID = task.id
+                    TaskRowView(
+                        task: task,
+                        taskID: taskID,
+                        isSelected: selectedTaskID == taskID,
+                        focusedField: $focusedField,
+                        onToggle: { toggleCompletion($0) },
+                        onTitleChange: { updateTitle($0, $1) },
+                        onDelete: { deleteTask($0) },
+                        onSelect: { selectTask($0) }
+                    )
+                }
+            }
+            .frame(maxWidth: .infinity, alignment: .topLeading)
+            .offset(y: -pullOffset)
+            .dropDestination(for: String.self) { items, location in
+                handleDrop(items: items)
+            }
+        }
+        .contentShape(Rectangle())
+        .onTapGesture {
+            handleBackgroundTap()
+        }
+        .focusable()
+        .focused($focusedField, equals: .scrollView)
+        .focusEffectDisabled()
+        .accessibilityIdentifier("task-list-scrollview")
+        .keyboardNavigation([
+            ShortcutKey(key: .upArrow): navigateUp,
+            ShortcutKey(key: .downArrow): navigateDown,
+            ShortcutKey(key: .space): toggleSelectedTask,
+            ShortcutKey(key: .return): focusSelectedTask,
+            ShortcutKey(key: .delete): deleteSelectedTask,
+        ])
+        .onAppear {
+            if focusedField == nil {
+                focusedField = .scrollView
+            }
+        }
+        .onChange(of: focusedField) { oldValue, newValue in
+            handleFocusChange(from: oldValue, to: newValue)
+
+            if newValue == nil {
+                if let pending = pendingFocus {
+                    print("🟣 onChange resolving pendingFocus: \(pending)")
+                    focusedField = pending
+                    pendingFocus = nil
+                } else {
+                    print("🟣 onChange repairing nil focus to .scrollView")
+                    focusedField = .scrollView
+                }
+            }
+        }
+        .onChange(of: undoManager, initial: true) { _, newValue in
+            managedObjectContext.undoManager = newValue
+        }
+        .toolbar {
+            platformToolbar
+        }
+    }
+}

diff --git a/ListlessiOS/Extensions/TaskListView+Drag.swift b/ListlessiOS/Extensions/TaskListView+Drag.swift

@@ -0,0 +1,44 @@
+import SwiftUI
+
+extension TaskListView {
+    func handleIOSDragChanged(taskID: UUID, point: CGPoint) {
+        guard let draggedID = draggedTaskID,
+              var order = visualOrder,
+              let currentIndex = order.firstIndex(of: draggedID),
+              let draggedFrame = rowFrames[draggedID] else { return }
+
+        let threshold = draggedFrame.height * 0.2
+
+        // Swap down: finger moved past the bottom edge of the dragged row
+        if currentIndex < order.count - 1 && point.y > draggedFrame.maxY + threshold {
+            order.swapAt(currentIndex, currentIndex + 1)
+            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
+                visualOrder = order
+            }
+            return
+        }
+
+        // Swap up: finger moved past the top edge of the dragged row
+        if currentIndex > 0 && point.y < draggedFrame.minY - threshold {
+            order.swapAt(currentIndex, currentIndex - 1)
+            withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
+                visualOrder = order
+            }
+        }
+    }
+
+    func commitIOSDrag() {
+        guard let draggedID = draggedTaskID,
+              let order = visualOrder,
+              let finalIndex = order.firstIndex(of: draggedID) else {
+            draggedTaskID = nil
+            visualOrder = nil
+            isDragging = false
+            return
+        }
+        store.moveTask(taskID: draggedID, toIndex: finalIndex)
+        draggedTaskID = nil
+        visualOrder = nil
+        isDragging = false
+    }
+}

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

@@ -0,0 +1,163 @@
+import SwiftUI
+
+struct TaskListView: View {
+    enum FocusField: Hashable {
+        case task(UUID)
+        case scrollView
+    }
+
+    @Environment(\.undoManager) var undoManager
+    @Environment(\.managedObjectContext) var managedObjectContext
+
+    @State var store: TaskStore
+    @FetchRequest(
+        sortDescriptors: [
+            NSSortDescriptor(keyPath: \TaskItem.isCompleted, ascending: true),
+            NSSortDescriptor(keyPath: \TaskItem.createdAt, ascending: true),
+        ],
+        animation: .default
+    )
+    var tasks: FetchedResults<TaskItem>
+    @FocusState var focusedField: FocusField?
+    @State var selectedTaskID: UUID?
+    @State private var refreshID = UUID()
+    @State var draggedTaskID: UUID?
+    @State var visualOrder: [UUID]?
+    @State var pendingFocus: FocusField?
+    @State var pullOffset: CGFloat = 0
+    @State var isDragging: Bool = false
+    @State var rowFrames: [UUID: CGRect] = [:]
+
+    var vStackSpacing: CGFloat { 12 }
+
+    init(store: TaskStore = TaskStore()) {
+        _store = State(wrappedValue: store)
+    }
+
+    func didStartDrag() {
+        isDragging = true
+        let generator = UIImpactFeedbackGenerator(style: .medium)
+        generator.impactOccurred()
+    }
+
+    var body: some View {
+        ScrollView {
+            VStack(alignment: .leading, spacing: vStackSpacing) {
+                navigationHeader
+                pullToCreateIndicatorRow
+                ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in
+                    let taskID = task.id
+                    TaskRowView(
+                        task: task,
+                        taskID: taskID,
+                        index: index,
+                        totalTasks: displayActiveTasks.count,
+                        isSelected: selectedTaskID == taskID,
+                        isDragging: $isDragging,
+                        focusedField: $focusedField,
+                        onToggle: { toggleCompletion($0) },
+                        onTitleChange: { updateTitle($0, $1) },
+                        onDelete: { deleteTask($0) },
+                        onSelect: { selectTask($0) },
+                        onStartEdit: { startEditing($0) },
+                        onEndEdit: { endEditing($0, shouldCreateNewTask: $1) }
+                    )
+                    .scaleEffect(draggedTaskID == taskID ? 1.05 : 1.0)
+                    .shadow(
+                        color: draggedTaskID == taskID ? .black.opacity(0.3) : .clear,
+                        radius: 12, y: 4
+                    )
+                    .zIndex(draggedTaskID == taskID ? 1 : 0)
+                    .taskDragGesture(
+                        isActive: !task.isCompleted,
+                        taskID: taskID,
+                        onDragStart: { startDrag(taskID: taskID) },
+                        onDragChanged: { point in handleIOSDragChanged(taskID: taskID, point: point) },
+                        onDragEnded: { commitIOSDrag() }
+                    )
+                    .onGeometryChange(for: CGRect.self) { proxy in
+                        proxy.frame(in: .global)
+                    } action: { frame in
+                        rowFrames[taskID] = frame
+                    }
+                }
+
+                ForEach(completedTasks) { task in
+                    let taskID = task.id
+                    TaskRowView(
+                        task: task,
+                        taskID: taskID,
+                        isSelected: selectedTaskID == taskID,
+                        focusedField: $focusedField,
+                        onToggle: { toggleCompletion($0) },
+                        onTitleChange: { updateTitle($0, $1) },
+                        onDelete: { deleteTask($0) },
+                        onSelect: { selectTask($0) }
+                    )
+                }
+            }
+            .frame(maxWidth: .infinity, alignment: .topLeading)
+            .padding(.trailing, 16)
+            .padding(.vertical, 12)
+            .offset(y: -pullOffset)
+        }
+        .scrollDisabled(draggedTaskID != nil)
+        .background {
+            Color.outerBackground.ignoresSafeArea()
+        }
+        .onScrollGeometryChange(for: CGFloat.self) { geo in
+            max(0, -(geo.contentOffset.y + geo.contentInsets.top))
+        } action: { _, pullDistance in
+            pullOffset = pullDistance
+        }
+        .onScrollPhaseChange { oldPhase, newPhase in
+            if oldPhase == .interacting && newPhase != .interacting {
+                if pullOffset >= pullCreateThreshold { createNewTaskAtTop() }
+                pullOffset = 0
+            }
+        }
+        .sensoryFeedback(.impact(weight: .medium), trigger: pullOffset >= pullCreateThreshold) { old, new in
+            !old && new
+        }
+        .contentShape(Rectangle())
+        .onTapGesture {
+            handleBackgroundTap()
+        }
+        .focusable()
+        .focused($focusedField, equals: .scrollView)
+        .focusEffectDisabled()
+        .accessibilityIdentifier("task-list-scrollview")
+        .keyboardNavigation([
+            ShortcutKey(key: .upArrow): navigateUp,
+            ShortcutKey(key: .downArrow): navigateDown,
+            ShortcutKey(key: .space): toggleSelectedTask,
+            ShortcutKey(key: .return): focusSelectedTask,
+            ShortcutKey(key: .delete): deleteSelectedTask,
+        ])
+        .onAppear {
+            if focusedField == nil {
+                focusedField = .scrollView
+            }
+        }
+        .onChange(of: focusedField) { oldValue, newValue in
+            handleFocusChange(from: oldValue, to: newValue)
+
+            if newValue == nil {
+                if let pending = pendingFocus {
+                    print("🟣 onChange resolving pendingFocus: \(pending)")
+                    focusedField = pending
+                    pendingFocus = nil
+                } else {
+                    print("🟣 onChange repairing nil focus to .scrollView")
+                    focusedField = .scrollView
+                }
+            }
+        }
+        .onChange(of: undoManager, initial: true) { _, newValue in
+            managedObjectContext.undoManager = newValue
+        }
+        .toolbar {
+            platformToolbar
+        }
+    }
+}