← All repositories

listless

A simple list app for Apple platforms

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


commit 61f90d7cd4c0681e709a2240e899e56614d773b9
parent 7d831d8ddffddb726ec25e3065572a83884564f4
Author: Michael Camilleri <[email protected]>
Date:   Wed, 18 Feb 2026 03:15:51 +0900

Restructure and add first version of drag-and-drop reordering on iOS

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

Diffstat

MListless.xcodeproj/project.pbxproj | 208+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
RListless/Views/AccentColor.swift -> Listless/Helpers/AccentColor.swift | 0
RListless/Views/KeyboardNavigationModifier.swift -> Listless/Helpers/KeyboardNavigationModifier.swift | 0
MListless/Views/TaskListView.swift | 170++++++++++++++++++++++++++++---------------------------------------------------
RListlessMac/Views/TaskListView+NavigationHeader.swift -> ListlessMac/Extensions/TaskListView+NavigationHeader.swift | 0
RListlessMac/Views/TaskListView+PullToCreate.swift -> ListlessMac/Extensions/TaskListView+PullToCreate.swift | 0
RListlessMac/Views/TaskListView+Toolbar.swift -> ListlessMac/Extensions/TaskListView+Toolbar.swift | 0
RListlessMac/Views/ClickableTextField.swift -> ListlessMac/Helpers/ClickableTextField.swift | 0
RListlessMac/Views/HoverCursorModifier.swift -> ListlessMac/Helpers/HoverCursorModifier.swift | 0
RListlessMac/Views/PlatformScrollIndicatorsModifier.swift -> ListlessMac/Helpers/PlatformScrollIndicatorsModifier.swift | 0
RListlessMac/Views/PlatformTextFieldWidthModifier.swift -> ListlessMac/Helpers/PlatformTextFieldWidthModifier.swift | 0
RListlessMac/Views/TaskRowDragGesture.swift -> ListlessMac/Helpers/TaskRowDragGesture.swift | 0
MListlessMac/Views/TaskRowView.swift | 3+++
RListlessiOS/Views/TaskListView+NavigationHeader.swift -> ListlessiOS/Extensions/TaskListView+NavigationHeader.swift | 0
RListlessiOS/Views/TaskListView+PullToCreate.swift -> ListlessiOS/Extensions/TaskListView+PullToCreate.swift | 0
RListlessiOS/Views/TaskListView+Toolbar.swift -> ListlessiOS/Extensions/TaskListView+Toolbar.swift | 0
RListlessiOS/Views/AppColors.swift -> ListlessiOS/Helpers/AppColors.swift | 0
RListlessiOS/Views/HoverCursorModifier.swift -> ListlessiOS/Helpers/HoverCursorModifier.swift | 0
RListlessiOS/Views/PlatformScrollIndicatorsModifier.swift -> ListlessiOS/Helpers/PlatformScrollIndicatorsModifier.swift | 0
RListlessiOS/Views/PlatformTextFieldWidthModifier.swift -> ListlessiOS/Helpers/PlatformTextFieldWidthModifier.swift | 0
RListlessiOS/Views/TappableTextField.swift -> ListlessiOS/Helpers/TappableTextField.swift | 0
AListlessiOS/Helpers/TaskRowDragGesture.swift | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
RListlessiOS/Views/TaskRowSwipeGesture.swift -> ListlessiOS/Helpers/TaskRowSwipeGesture.swift | 39+++++++++++++++++++++++----------------
DListlessiOS/Views/TaskRowDragGesture.swift | 108-------------------------------------------------------------------------------
MListlessiOS/Views/TaskRowView.swift | 12+++++++++++-

25 files changed, 274 insertions(+), 320 deletions(-)

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

@@ -7,48 +7,48 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		074A81D9DAF58E8E088CBC89 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431B0EA72D590AF0CC868515 /* TaskListView+Toolbar.swift */; };
 		0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
-		15B71073767FB4766A6BA2BE /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */; };
+		0F12D56A528FCBF8A67864CB /* TappableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECE0E961F87BA32FA87BF90 /* TappableTextField.swift */; };
+		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 */; };
-		1E3857004DDD888BB4A6ED50 /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0630FFEEFF7662A414E9A8 /* AccentColor.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 */; };
-		3FCEFB586D9A9085A201AE7D /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BF73E1B8FE337B76D3E757 /* TaskListView+PullToCreate.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 */; };
-		5CFFBC8A17E065640857540A /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0630FFEEFF7662A414E9A8 /* AccentColor.swift */; };
+		5D3EE9526DA269EE9EE3AB52 /* AppColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E998119283F784B9ADEE28 /* AppColors.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 */; };
-		7B2CD636BA3B63A586F93E31 /* TaskRowSwipeGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44C16D36971A140364159FB9 /* TaskRowSwipeGesture.swift */; };
+		763363F6F3C7D2D3C9A63977 /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */; };
+		77970A2323A598E830D95301 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */; };
+		77FE96F070B1F7FE31A9CE51 /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */; };
 		7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */; };
-		83378A0575640417ED41FC25 /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2BB7ABCABA4EB67180A200 /* ClickableTextField.swift */; };
+		80BCA654FE694D8A77D9BF93 /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */; };
+		882695E7AE463C0F39ACFF3C /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */; };
 		8E08F4C82D6F9E67667CB20A /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
-		8E680EC18B0339931E785F0C /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */; };
-		907478D07E5CCD4966579306 /* TaskListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0C28F7AF2AFAF677F678087 /* TaskListView+NavigationHeader.swift */; };
 		91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
-		92932AEC64B65AD6F95A1E42 /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF51ACA55E28AA1A11D3962C /* TaskListView+PullToCreate.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 */; };
-		A8D13DD6196772ECA146CF2A /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D74AE2501F35974F57D21F /* PlatformScrollIndicatorsModifier.swift */; };
-		AB5D681B98FC1D2FD7BF2FAA /* TaskListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7AF738EEB6ABDF86B0B781F /* TaskListView+NavigationHeader.swift */; };
-		C1BD61D6DFA687E9CB9ACA60 /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */; };
+		BA6953E0EFE6F8255F05A3FD /* AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */; };
 		C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
-		D6B3DBDD6A3F0A6E166CFFD5 /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */; };
-		D878CD3A552C6A9685A30AA8 /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */; };
-		D8EFF49E9156083D675D47F0 /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; };
-		D9DA553485AD0CA28D5BE0C8 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */; };
-		DC71C45D92524C3893BF9FDB /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */; };
+		CAC6A40C680611163775CDFE /* TaskListView+NavigationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E67FE94ED8E9B248A0C3A97 /* TaskListView+NavigationHeader.swift */; };
+		DB5FF6C1AA57D4C9BDDD50FD /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */; };
 		DC73A39A269AB495BCE1AC48 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
-		E3C6D4AE5E729D34086F132D /* TappableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95568F6A132636D04B8CF593 /* TappableTextField.swift */; };
+		DEE187A790A4058FE4AFDB2E /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */; };
 		E47136CA7428927395D8C7C7 /* PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF7D84B54AE70036D205CA4 /* PullToCreate.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 */; };
-		FEC96DAA2BF8BB5D6D8504EB /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */; };
+		F6587B84ECC6BFE92A5FB493 /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -63,53 +63,53 @@
 
 /* Begin PBXFileReference section */
 		01E141436176F83594E2F26B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
-		0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardNavigationModifier.swift; sourceTree = "<group>"; };
-		0F2BB7ABCABA4EB67180A200 /* ClickableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickableTextField.swift; sourceTree = "<group>"; };
+		067D666DF0AF1C53404ECF7C /* TaskRowSwipeGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowSwipeGesture.swift; sourceTree = "<group>"; };
 		126108860D7878DDC3BECC4B /* Listless iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "Listless iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
-		138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
+		17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentColor.swift; sourceTree = "<group>"; };
 		199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
-		1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
 		1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessMacApp.swift; sourceTree = "<group>"; };
+		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>"; };
-		431B0EA72D590AF0CC868515 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
-		44C16D36971A140364159FB9 /* TaskRowSwipeGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowSwipeGesture.swift; sourceTree = "<group>"; };
-		48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
+		3E67FE94ED8E9B248A0C3A97 /* TaskListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+NavigationHeader.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>"; };
+		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>"; };
 		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>"; };
-		93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
 		944BAE054AAC1B9C4FC954F9 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
-		95568F6A132636D04B8CF593 /* TappableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableTextField.swift; sourceTree = "<group>"; };
-		95BF73E1B8FE337B76D3E757 /* TaskListView+PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToCreate.swift"; sourceTree = "<group>"; };
 		967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreTests.swift; sourceTree = "<group>"; };
+		9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToCreate.swift"; sourceTree = "<group>"; };
 		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>"; };
-		A0C28F7AF2AFAF677F678087 /* TaskListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+NavigationHeader.swift"; sourceTree = "<group>"; };
-		A7AF738EEB6ABDF86B0B781F /* TaskListView+NavigationHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+NavigationHeader.swift"; 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>"; };
-		B4D74AE2501F35974F57D21F /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.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>"; };
 		C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
 		C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = "Listless iOS Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		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>"; };
-		D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; 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>"; };
-		DF51ACA55E28AA1A11D3962C /* TaskListView+PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+PullToCreate.swift"; sourceTree = "<group>"; };
 		E06485DBE35B60868E14202A /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
-		EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
+		E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.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>"; };
+		FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
 		FBB8A3BEB346267B30B4675F /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = "<group>"; };
-		FD1FE1858BC9E8915A091D33 /* AppColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppColors.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXGroup section */
@@ -117,13 +117,25 @@
 			isa = PBXGroup;
 			children = (
 				D123BB181208FC825777B0A7 /* .gitkeep */,
-				7F0630FFEEFF7662A414E9A8 /* AccentColor.swift */,
-				0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */,
 				537A913AC421BAEF60D26D9C /* TaskListView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
 		};
+		19A2FAA566465414CA20E6D8 /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				F1E998119283F784B9ADEE28 /* AppColors.swift */,
+				B7588879D0FA1C2A8BCEF14F /* HoverCursorModifier.swift */,
+				FA5D58EC1FBAA96E83A79445 /* PlatformScrollIndicatorsModifier.swift */,
+				E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */,
+				6ECE0E961F87BA32FA87BF90 /* TappableTextField.swift */,
+				D57C3CC81C4380EFAE4DB910 /* TaskRowDragGesture.swift */,
+				067D666DF0AF1C53404ECF7C /* TaskRowSwipeGesture.swift */,
+			);
+			path = Helpers;
+			sourceTree = "<group>";
+		};
 		3936BDEE64D16E6C4C85B3DD /* Products */ = {
 			isa = PBXGroup;
 			children = (
@@ -134,9 +146,22 @@
 			name = Products;
 			sourceTree = "<group>";
 		};
+		42CC0393E2524624AEC54D03 /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */,
+				9F5D8B5866362D422A2A331C /* HoverCursorModifier.swift */,
+				466F9B0E407DF1F5B4789531 /* PlatformScrollIndicatorsModifier.swift */,
+				E8448C5778F75F52719114AF /* PlatformTextFieldWidthModifier.swift */,
+				F416DD868A4C044F0D64F8D0 /* TaskRowDragGesture.swift */,
+			);
+			path = Helpers;
+			sourceTree = "<group>";
+		};
 		58051CBDE2390F9E13647235 /* Listless */ = {
 			isa = PBXGroup;
 			children = (
+				78E0184C210B140892690CD4 /* Helpers */,
 				AA563E991F69ED14DCD9A1AB /* Infrastructure */,
 				8656EEF3161BE20196B8042E /* Models */,
 				E98EF3B4638A9E8473EA62FA /* Sync */,
@@ -148,22 +173,31 @@
 		58F917D865E0BDF4EF282306 /* Views */ = {
 			isa = PBXGroup;
 			children = (
-				FD1FE1858BC9E8915A091D33 /* AppColors.swift */,
-				82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */,
-				48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */,
-				81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */,
 				BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */,
-				95568F6A132636D04B8CF593 /* TappableTextField.swift */,
-				A0C28F7AF2AFAF677F678087 /* TaskListView+NavigationHeader.swift */,
-				DF51ACA55E28AA1A11D3962C /* TaskListView+PullToCreate.swift */,
-				93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */,
-				1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */,
-				44C16D36971A140364159FB9 /* TaskRowSwipeGesture.swift */,
 				199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
 		};
+		78E0184C210B140892690CD4 /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				17DD7EDA74DAAFA27C84CA08 /* AccentColor.swift */,
+				4669414A460FC0758D5B49A8 /* KeyboardNavigationModifier.swift */,
+			);
+			path = Helpers;
+			sourceTree = "<group>";
+		};
+		8629C1C94770B3B0D08B580D /* Extensions */ = {
+			isa = PBXGroup;
+			children = (
+				712ADAD0FE66175DAA9A6D50 /* TaskListView+NavigationHeader.swift */,
+				9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */,
+				CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */,
+			);
+			path = Extensions;
+			sourceTree = "<group>";
+		};
 		8656EEF3161BE20196B8042E /* Models */ = {
 			isa = PBXGroup;
 			children = (
@@ -181,6 +215,8 @@
 				3313FEDB101EECA4B344EEF4 /* Info.plist */,
 				9262207DAC21619BD9EDEE15 /* Listless.entitlements */,
 				AC245331D715EA85887C0BA0 /* ListlessiOSApp.swift */,
+				8629C1C94770B3B0D08B580D /* Extensions */,
+				19A2FAA566465414CA20E6D8 /* Helpers */,
 				58F917D865E0BDF4EF282306 /* Views */,
 			);
 			path = ListlessiOS;
@@ -197,14 +233,6 @@
 		D12ECC901ABED96B86CC85B5 /* Views */ = {
 			isa = PBXGroup;
 			children = (
-				0F2BB7ABCABA4EB67180A200 /* ClickableTextField.swift */,
-				D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */,
-				B4D74AE2501F35974F57D21F /* PlatformScrollIndicatorsModifier.swift */,
-				EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */,
-				A7AF738EEB6ABDF86B0B781F /* TaskListView+NavigationHeader.swift */,
-				95BF73E1B8FE337B76D3E757 /* TaskListView+PullToCreate.swift */,
-				431B0EA72D590AF0CC868515 /* TaskListView+Toolbar.swift */,
-				138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */,
 				E06485DBE35B60868E14202A /* TaskRowView.swift */,
 			);
 			path = Views;
@@ -216,6 +244,8 @@
 				01E141436176F83594E2F26B /* Info.plist */,
 				7C73E9D4C42CCABBF0F33543 /* Listless.entitlements */,
 				1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */,
+				F7279CE4B2501F9F5111A3D8 /* Extensions */,
+				42CC0393E2524624AEC54D03 /* Helpers */,
 				D12ECC901ABED96B86CC85B5 /* Views */,
 			);
 			path = ListlessMac;
@@ -265,6 +295,16 @@
 			path = Tests/Unit;
 			sourceTree = "<group>";
 		};
+		F7279CE4B2501F9F5111A3D8 /* Extensions */ = {
+			isa = PBXGroup;
+			children = (
+				3E67FE94ED8E9B248A0C3A97 /* TaskListView+NavigationHeader.swift */,
+				A741111B4DEB3A7406A95C93 /* TaskListView+PullToCreate.swift */,
+				2A440253D045A0896D94ADD0 /* TaskListView+Toolbar.swift */,
+			);
+			path = Extensions;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -369,21 +409,21 @@
 			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 */,
+				99D17075DA3F00F52A18BB4D /* AccentColor.swift in Sources */,
+				DB5FF6C1AA57D4C9BDDD50FD /* ClickableTextField.swift in Sources */,
+				882695E7AE463C0F39ACFF3C /* HoverCursorModifier.swift in Sources */,
+				172F2DD978988E207610055F /* KeyboardNavigationModifier.swift in Sources */,
 				96617677059FABDBB80D642B /* Listless.xcdatamodeld in Sources */,
 				614FCCA450EC0BFFD8B40640 /* ListlessMacApp.swift in Sources */,
 				A0AA8FD4C542E9AEB2437BC2 /* PersistenceController.swift in Sources */,
-				A8D13DD6196772ECA146CF2A /* PlatformScrollIndicatorsModifier.swift in Sources */,
-				D878CD3A552C6A9685A30AA8 /* PlatformTextFieldWidthModifier.swift in Sources */,
+				5761B201BF46FCA9C5C98CEF /* PlatformScrollIndicatorsModifier.swift in Sources */,
+				DEE187A790A4058FE4AFDB2E /* PlatformTextFieldWidthModifier.swift in Sources */,
 				C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */,
-				AB5D681B98FC1D2FD7BF2FAA /* TaskListView+NavigationHeader.swift in Sources */,
-				3FCEFB586D9A9085A201AE7D /* TaskListView+PullToCreate.swift in Sources */,
-				074A81D9DAF58E8E088CBC89 /* TaskListView+Toolbar.swift in Sources */,
+				CAC6A40C680611163775CDFE /* TaskListView+NavigationHeader.swift in Sources */,
+				2374527E3629DD87AE3D2761 /* TaskListView+PullToCreate.swift in Sources */,
+				322EE74CBED492693B92AD59 /* TaskListView+Toolbar.swift in Sources */,
 				8E08F4C82D6F9E67667CB20A /* TaskListView.swift in Sources */,
-				C1BD61D6DFA687E9CB9ACA60 /* TaskRowDragGesture.swift in Sources */,
+				5035EC4C7518A5FF9AD454CA /* TaskRowDragGesture.swift in Sources */,
 				3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */,
 				3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */,
 			);
@@ -393,24 +433,24 @@
 			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 */,
+				BA6953E0EFE6F8255F05A3FD /* AccentColor.swift in Sources */,
+				5D3EE9526DA269EE9EE3AB52 /* AppColors.swift in Sources */,
+				E60F81C7B930AACE67746759 /* HoverCursorModifier.swift in Sources */,
+				F6587B84ECC6BFE92A5FB493 /* KeyboardNavigationModifier.swift in Sources */,
 				5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */,
 				F0B2B806BD84A4F2FDF8E038 /* ListlessiOSApp.swift in Sources */,
 				DC73A39A269AB495BCE1AC48 /* PersistenceController.swift in Sources */,
-				8E680EC18B0339931E785F0C /* PlatformScrollIndicatorsModifier.swift in Sources */,
-				DC71C45D92524C3893BF9FDB /* PlatformTextFieldWidthModifier.swift in Sources */,
+				763363F6F3C7D2D3C9A63977 /* PlatformScrollIndicatorsModifier.swift in Sources */,
+				77FE96F070B1F7FE31A9CE51 /* PlatformTextFieldWidthModifier.swift in Sources */,
 				E47136CA7428927395D8C7C7 /* PullToCreate.swift in Sources */,
-				E3C6D4AE5E729D34086F132D /* TappableTextField.swift in Sources */,
+				0F12D56A528FCBF8A67864CB /* TappableTextField.swift in Sources */,
 				0ACA67F6578EFF181EE5C9A7 /* TaskItem.swift in Sources */,
-				907478D07E5CCD4966579306 /* TaskListView+NavigationHeader.swift in Sources */,
-				92932AEC64B65AD6F95A1E42 /* TaskListView+PullToCreate.swift in Sources */,
-				D9DA553485AD0CA28D5BE0C8 /* TaskListView+Toolbar.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 */,
-				D6B3DBDD6A3F0A6E166CFFD5 /* TaskRowDragGesture.swift in Sources */,
-				7B2CD636BA3B63A586F93E31 /* TaskRowSwipeGesture.swift in Sources */,
+				2C53717A0EAFA39240615C9A /* TaskRowDragGesture.swift in Sources */,
+				182D9FB61E3A3650D7D83D2A /* TaskRowSwipeGesture.swift in Sources */,
 				7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */,
 				91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */,
 			);

diff --git a/Listless/Views/AccentColor.swift b/Listless/Helpers/AccentColor.swift

diff --git a/Listless/Views/KeyboardNavigationModifier.swift b/Listless/Helpers/KeyboardNavigationModifier.swift

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

@@ -22,18 +22,12 @@ struct TaskListView: View {
     @State var selectedTaskID: UUID?
     @State private var refreshID = UUID()
     @State private var draggedTaskID: UUID?
-    @State private var swipingTaskID: UUID?
     @State private var visualOrder: [UUID]?
     @State private var pendingFocus: FocusField?
     @State var pullOffset: CGFloat = 0
     #if os(iOS)
-    private struct IOSDragState {
-        let taskID: UUID
-        var fingerPosition: CGPoint
-    }
-    @State private var iosDragState: IOSDragState? = nil
+    @State private var isDragging: Bool = false
     @State private var rowFrames: [UUID: CGRect] = [:]
-    @State private var scrollViewMinY: CGFloat = 0
     #endif
 
     init(store: TaskStore = TaskStore()) {
@@ -48,39 +42,34 @@ struct TaskListView: View {
                 ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in
                     let taskID = task.id
                     #if os(iOS)
-                    Group {
-                        if iosDragState?.taskID == taskID {
-                            // Ghost spacer: placeholder silhouette while card is floating
-                            UnevenRoundedRectangle(
-                                topLeadingRadius: 0, bottomLeadingRadius: 0,
-                                bottomTrailingRadius: 14, topTrailingRadius: 14
-                            )
-                            .fill(Color.taskCard.opacity(0.4))
-                            .frame(height: rowFrames[taskID]?.height ?? 56)
-                        } 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 && swipingTaskID == nil,
-                                taskID: taskID,
-                                onDragStart: { startDrag(taskID: taskID) },
-                                onDragChanged: { point in handleIOSDragChanged(taskID: taskID, point: point) },
-                                onDragEnded: { commitIOSDrag() }
-                            )
-                        }
-                    }
+                    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.03 : 1.0)
+                    .shadow(
+                        color: draggedTaskID == taskID ? .black.opacity(0.15) : .clear,
+                        radius: 8, y: 2
+                    )
+                    .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
@@ -102,7 +91,7 @@ struct TaskListView: View {
                         onEndEdit: { endEditing($0, shouldCreateNewTask: $1) }
                     )
                     .taskDragGesture(
-                        isActive: !task.isCompleted && swipingTaskID == nil,
+                        isActive: !task.isCompleted,
                         taskID: task.id,
                         onDragStart: { startDrag(taskID: task.id) }
                     )
@@ -192,50 +181,10 @@ struct TaskListView: View {
             #endif
         }
         #if os(iOS)
+        .scrollDisabled(draggedTaskID != nil)
         .background {
             Color.outerBackground.ignoresSafeArea()
         }
-        .overlay(alignment: .topLeading) {
-            if let state = iosDragState,
-               let task = activeTasks.first(where: { $0.id == state.taskID }),
-               let frame = rowFrames[state.taskID] {
-                HStack(spacing: 0) {
-                    Rectangle()
-                        .fill(
-                            taskColor(
-                                forIndex: visualOrder?.firstIndex(of: state.taskID) ?? 0,
-                                total: displayActiveTasks.count
-                            )
-                        )
-                        .frame(width: 8)
-                    Text(task.title.isEmpty ? "New task" : task.title)
-                        .font(.body)
-                        .frame(maxWidth: .infinity, alignment: .leading)
-                        .padding()
-                        .background(Color.taskCard)
-                }
-                .clipShape(
-                    UnevenRoundedRectangle(
-                        topLeadingRadius: 0, bottomLeadingRadius: 0,
-                        bottomTrailingRadius: 14, topTrailingRadius: 14
-                    )
-                )
-                .shadow(color: .black.opacity(0.18), radius: 10, y: 4)
-                .frame(width: frame.width, height: frame.height)
-                .position(
-                    x: UIScreen.main.bounds.midX,
-                    y: state.fingerPosition.y - scrollViewMinY
-                )
-                .ignoresSafeArea()
-                .allowsHitTesting(false)
-                .zIndex(100)
-            }
-        }
-        .onGeometryChange(for: CGFloat.self) { proxy in
-            proxy.frame(in: .global).minY
-        } action: { minY in
-            scrollViewMinY = minY
-        }
         .onScrollGeometryChange(for: CGFloat.self) { geo in
             max(0, -(geo.contentOffset.y + geo.contentInsets.top))
         } action: { _, pullDistance in
@@ -446,10 +395,6 @@ struct TaskListView: View {
         deleteTask(task)
     }
 
-    private func handleSwipeActiveChanged(_ taskID: UUID, _ isActive: Bool) {
-        swipingTaskID = isActive ? taskID : nil
-    }
-
     private func selectTask(_ taskID: UUID) {
         selectedTaskID = taskID
     }
@@ -631,10 +576,13 @@ struct TaskListView: View {
     // MARK: - Drag and Drop
 
     private func startDrag(taskID: UUID) {
+        guard draggedTaskID == nil else { return }
         draggedTaskID = taskID
         visualOrder = activeTasks.map(\.id)
         #if os(iOS)
-        iosDragState = IOSDragState(taskID: taskID, fingerPosition: .zero)
+        isDragging = true
+        let generator = UIImpactFeedbackGenerator(style: .medium)
+        generator.impactOccurred()
         #endif
     }
 
@@ -730,45 +678,45 @@ struct TaskListView: View {
     // MARK: - iOS Drag Helpers
 
     #if os(iOS)
-    private func dropIndexForY(_ y: CGFloat) -> Int {
-        let midpoints = displayActiveTasks.enumerated().compactMap { i, task -> (Int, CGFloat)? in
-            guard let frame = rowFrames[task.id] else { return nil }
-            return (i, frame.midY)
-        }
-        return midpoints.first(where: { $0.1 > y })?.0 ?? displayActiveTasks.count
-    }
+    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 }
 
-    private func updateVisualOrderToIndex(_ index: Int) {
-        guard let draggedID = draggedTaskID, let order = visualOrder else { return }
-        var newOrder = order.filter { $0 != draggedID }
-        let clamped = min(max(0, index), newOrder.count)
-        newOrder.insert(draggedID, at: clamped)
-        if newOrder != visualOrder {
+        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 = newOrder
+                visualOrder = order
             }
+            return
         }
-    }
 
-    private func handleIOSDragChanged(taskID: UUID, point: CGPoint) {
-        iosDragState?.fingerPosition = point
-        let index = dropIndexForY(point.y)
-        updateVisualOrderToIndex(index)
+        // 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 state = iosDragState,
+        guard let draggedID = draggedTaskID,
               let order = visualOrder,
-              let finalIndex = order.firstIndex(of: state.taskID) else {
-            iosDragState = nil
+              let finalIndex = order.firstIndex(of: draggedID) else {
             draggedTaskID = nil
             visualOrder = nil
+            isDragging = false
             return
         }
-        store.moveTask(taskID: state.taskID, toIndex: finalIndex)
-        iosDragState = nil
+        store.moveTask(taskID: draggedID, toIndex: finalIndex)
         draggedTaskID = nil
         visualOrder = nil
+        isDragging = false
     }
 
     #endif

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

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

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

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

diff --git a/ListlessMac/Views/HoverCursorModifier.swift b/ListlessMac/Helpers/HoverCursorModifier.swift

diff --git a/ListlessMac/Views/PlatformScrollIndicatorsModifier.swift b/ListlessMac/Helpers/PlatformScrollIndicatorsModifier.swift

diff --git a/ListlessMac/Views/PlatformTextFieldWidthModifier.swift b/ListlessMac/Helpers/PlatformTextFieldWidthModifier.swift

diff --git a/ListlessMac/Views/TaskRowDragGesture.swift b/ListlessMac/Helpers/TaskRowDragGesture.swift

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

@@ -6,6 +6,7 @@ struct TaskRowView: View {
     let index: Int
     let totalTasks: Int
     let isSelected: Bool
+    let isDragging: Bool
     let isEditing: Bool
     let onToggle: (TaskItem) -> Void
     let onTitleChange: (TaskItem, String) -> Void
@@ -38,6 +39,7 @@ struct TaskRowView: View {
         index: Int = 0,
         totalTasks: Int = 1,
         isSelected: Bool,
+        isDragging: Bool = false,
         isEditing: Bool = false,
         focusedField: FocusState<TaskListView.FocusField?>.Binding,
         onToggle: @escaping (TaskItem) -> Void,
@@ -52,6 +54,7 @@ struct TaskRowView: View {
         self.index = index
         self.totalTasks = totalTasks
         self.isSelected = isSelected
+        self.isDragging = isDragging
         self.isEditing = isEditing
         self.onToggle = onToggle
         self.onTitleChange = onTitleChange

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

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

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

diff --git a/ListlessiOS/Views/AppColors.swift b/ListlessiOS/Helpers/AppColors.swift

diff --git a/ListlessiOS/Views/HoverCursorModifier.swift b/ListlessiOS/Helpers/HoverCursorModifier.swift

diff --git a/ListlessiOS/Views/PlatformScrollIndicatorsModifier.swift b/ListlessiOS/Helpers/PlatformScrollIndicatorsModifier.swift

diff --git a/ListlessiOS/Views/PlatformTextFieldWidthModifier.swift b/ListlessiOS/Helpers/PlatformTextFieldWidthModifier.swift

diff --git a/ListlessiOS/Views/TappableTextField.swift b/ListlessiOS/Helpers/TappableTextField.swift

diff --git a/ListlessiOS/Helpers/TaskRowDragGesture.swift b/ListlessiOS/Helpers/TaskRowDragGesture.swift

@@ -0,0 +1,54 @@
+import SwiftUI
+
+extension View {
+    func taskDragGesture(
+        isActive: Bool,
+        taskID: UUID,
+        onDragStart: @escaping () -> Void,
+        onDragChanged: @escaping (CGPoint) -> Void,
+        onDragEnded: @escaping () -> Void
+    ) -> some View {
+        self.modifier(
+            TaskRowDragGesture(
+                isActive: isActive,
+                taskID: taskID,
+                onDragStart: onDragStart,
+                onDragChanged: onDragChanged,
+                onDragEnded: onDragEnded
+            ))
+    }
+}
+
+struct TaskRowDragGesture: ViewModifier {
+    let isActive: Bool
+    let taskID: UUID
+    let onDragStart: () -> Void
+    let onDragChanged: (CGPoint) -> Void
+    let onDragEnded: () -> Void
+
+    func body(content: Content) -> some View {
+        content
+            .simultaneousGesture(
+                LongPressGesture(minimumDuration: 0.4)
+                    .sequenced(before: DragGesture(minimumDistance: 0, coordinateSpace: .global))
+                    .onChanged { value in
+                        switch value {
+                        case .second(true, let drag):
+                            if let drag {
+                                // onDragStart is idempotent (guarded in TaskListView);
+                                // wait for a real drag value so the overlay has a valid
+                                // position from the very first frame.
+                                onDragStart()
+                                onDragChanged(drag.location)
+                            }
+                        default:
+                            break
+                        }
+                    }
+                    .onEnded { _ in
+                        onDragEnded()
+                    },
+                including: isActive ? .all : .none
+            )
+    }
+}

diff --git a/ListlessiOS/Views/TaskRowSwipeGesture.swift b/ListlessiOS/Helpers/TaskRowSwipeGesture.swift

@@ -5,14 +5,13 @@ extension View {
     func taskSwipeGesture(
         isActive: Bool,
         isEditing: Bool,
-        isDragging: Bool,
+        isDragging: Binding<Bool>,
         swipeOffset: Binding<CGFloat>,
         swipeDirection: Binding<TaskRowSwipeGesture.SwipeDirection>,
         isTriggered: Binding<Bool>,
         completeColor: Color = .green,
         onComplete: @escaping () -> Void,
-        onDelete: @escaping () -> Void,
-        onSwipeActiveChanged: @escaping (Bool) -> Void = { _ in }
+        onDelete: @escaping () -> Void
     ) -> some View {
         self.modifier(
             TaskRowSwipeGesture(
@@ -24,8 +23,7 @@ extension View {
                 isTriggered: isTriggered,
                 completeColor: completeColor,
                 onComplete: onComplete,
-                onDelete: onDelete,
-                onSwipeActiveChanged: onSwipeActiveChanged
+                onDelete: onDelete
             ))
     }
 }
@@ -33,14 +31,13 @@ extension View {
 struct TaskRowSwipeGesture: ViewModifier {
     let isActive: Bool
     let isEditing: Bool
-    let isDragging: Bool
+    @Binding var isDragging: Bool
     @Binding var swipeOffset: CGFloat
     @Binding var swipeDirection: SwipeDirection
     @Binding var isTriggered: Bool
     let completeColor: Color
     let onComplete: () -> Void
     let onDelete: () -> Void
-    let onSwipeActiveChanged: (Bool) -> Void
 
     enum SwipeDirection: Equatable {
         case left
@@ -68,6 +65,7 @@ struct TaskRowSwipeGesture: ViewModifier {
         }
         .gesture(
             SwipePanGesture(
+                isEnabled: isActive && !isDragging,
                 onChanged: { translation in
                     guard isActive, !isEditing, !isDragging else { return }
                     handleDragChanged(
@@ -111,11 +109,6 @@ struct TaskRowSwipeGesture: ViewModifier {
             return
         }
 
-        // Determine direction and notify that swipe is active
-        if swipeDirection == .none {
-            onSwipeActiveChanged(true)
-        }
-
         if horizontalTranslation > 0 {
             swipeDirection = .right
         } else if horizontalTranslation < 0 {
@@ -134,6 +127,13 @@ struct TaskRowSwipeGesture: ViewModifier {
     }
 
     private func handleDragEnded() {
+        guard !isDragging else {
+            // A drag-reorder started during or after this swipe — spring back, no action.
+            withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
+                resetSwipeState()
+            }
+            return
+        }
         if isTriggered {
             if swipeDirection == .right {
                 // Complete: spring back and let SwiftUI animate the row to the completed section
@@ -170,9 +170,6 @@ struct TaskRowSwipeGesture: ViewModifier {
     }
 
     private func resetSwipeState() {
-        if swipeDirection != .none {
-            onSwipeActiveChanged(false)
-        }
         swipeOffset = 0
         swipeDirection = .none
         isTriggered = false
@@ -190,6 +187,7 @@ struct TaskRowSwipeGesture: ViewModifier {
 /// recognizer; SwiftUI manages the lifecycle automatically — no manual
 /// UIView host-finding or marker-based hit-testing needed.
 private struct SwipePanGesture: UIGestureRecognizerRepresentable {
+    let isEnabled: Bool
     let onChanged: (CGPoint) -> Void
     let onEnded: () -> Void
 
@@ -202,6 +200,10 @@ private struct SwipePanGesture: UIGestureRecognizerRepresentable {
         return pan
     }
 
+    func updateUIGestureRecognizer(_ recognizer: UIPanGestureRecognizer, context: Context) {
+        context.coordinator.isEnabled = isEnabled
+    }
+
     func handleUIGestureRecognizerAction(
         _ recognizer: UIPanGestureRecognizer, context: Context
     ) {
@@ -220,6 +222,12 @@ private struct SwipePanGesture: UIGestureRecognizerRepresentable {
     }
 
     final class Coordinator: NSObject, UIGestureRecognizerDelegate {
+        var isEnabled: Bool = true
+
+        func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+            isEnabled
+        }
+
         func gestureRecognizer(
             _ gestureRecognizer: UIGestureRecognizer,
             shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
@@ -228,4 +236,3 @@ private struct SwipePanGesture: UIGestureRecognizerRepresentable {
         }
     }
 }
-

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

@@ -1,108 +0,0 @@
-import SwiftUI
-import UIKit
-
-extension View {
-    func taskDragGesture(
-        isActive: Bool,
-        taskID: UUID,
-        onDragStart: @escaping () -> Void,
-        onDragChanged: @escaping (CGPoint) -> Void,
-        onDragEnded: @escaping () -> Void
-    ) -> some View {
-        self.modifier(
-            TaskRowDragGesture(
-                isActive: isActive,
-                taskID: taskID,
-                onDragStart: onDragStart,
-                onDragChanged: onDragChanged,
-                onDragEnded: onDragEnded
-            ))
-    }
-}
-
-struct TaskRowDragGesture: ViewModifier {
-    let isActive: Bool
-    let taskID: UUID
-    let onDragStart: () -> Void
-    let onDragChanged: (CGPoint) -> Void
-    let onDragEnded: () -> Void
-
-    func body(content: Content) -> some View {
-        content
-            .gesture(
-                LongPressDragGesture(
-                    isActive: isActive,
-                    onDragStart: onDragStart,
-                    onDragChanged: onDragChanged,
-                    onDragEnded: onDragEnded
-                )
-            )
-    }
-}
-
-// MARK: - UIKit Long Press + Drag via UIGestureRecognizerRepresentable
-
-/// A UILongPressGestureRecognizer bridged into SwiftUI. Fires onDragStart after the
-/// minimum hold duration, then tracks finger movement via onDragChanged (window coords),
-/// and calls onDragEnded when the touch ends or is cancelled.
-private struct LongPressDragGesture: UIGestureRecognizerRepresentable {
-    let isActive: Bool
-    let onDragStart: () -> Void
-    let onDragChanged: (CGPoint) -> Void
-    let onDragEnded: () -> Void
-
-    func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer {
-        let recognizer = UILongPressGestureRecognizer()
-        recognizer.minimumPressDuration = 0.4
-        recognizer.allowableMovement = .infinity
-        recognizer.cancelsTouchesInView = false
-        recognizer.delegate = context.coordinator
-        return recognizer
-    }
-
-    func updateUIGestureRecognizer(_ recognizer: UILongPressGestureRecognizer, context: Context) {
-        recognizer.isEnabled = isActive
-    }
-
-    func handleUIGestureRecognizerAction(
-        _ recognizer: UILongPressGestureRecognizer, context: Context
-    ) {
-        switch recognizer.state {
-        case .began:
-            onDragStart()
-            if let window = recognizer.view?.window {
-                onDragChanged(recognizer.location(in: window))
-            }
-        case .changed:
-            if let window = recognizer.view?.window {
-                onDragChanged(recognizer.location(in: window))
-            }
-        case .ended, .cancelled, .failed:
-            onDragEnded()
-        default:
-            break
-        }
-    }
-
-    func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
-        Coordinator()
-    }
-
-    final class Coordinator: NSObject, UIGestureRecognizerDelegate {
-        func gestureRecognizer(
-            _ gestureRecognizer: UIGestureRecognizer,
-            shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
-        ) -> Bool {
-            // Allow simultaneous recognition with the scroll view's pan gesture
-            if let pan = otherGestureRecognizer as? UIPanGestureRecognizer,
-               pan.view is UIScrollView {
-                return true
-            }
-            // Prevent simultaneous recognition with the swipe UIPanGestureRecognizer
-            if otherGestureRecognizer is UIPanGestureRecognizer {
-                return false
-            }
-            return true
-        }
-    }
-}

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

@@ -6,6 +6,7 @@ struct TaskRowView: View {
     let index: Int
     let totalTasks: Int
     let isSelected: Bool
+    @Binding var isDragging: Bool
     let onToggle: (TaskItem) -> Void
     let onTitleChange: (TaskItem, String) -> Void
     let onDelete: (TaskItem) -> Void
@@ -27,6 +28,7 @@ struct TaskRowView: View {
         index: Int = 0,
         totalTasks: Int = 1,
         isSelected: Bool,
+        isDragging: Binding<Bool> = .constant(false),
         isEditing: Bool = false,
         focusedField: FocusState<TaskListView.FocusField?>.Binding,
         onToggle: @escaping (TaskItem) -> Void,
@@ -41,6 +43,7 @@ struct TaskRowView: View {
         self.index = index
         self.totalTasks = totalTasks
         self.isSelected = isSelected
+        _isDragging = isDragging
         self.onToggle = onToggle
         self.onTitleChange = onTitleChange
         self.onDelete = onDelete
@@ -142,10 +145,17 @@ struct TaskRowView: View {
         .onChange(of: totalTasks) { _, _ in
             cachedAccentColor = computeAccentColor()
         }
+        .onChange(of: isDragging) { _, dragging in
+            if dragging {
+                swipeOffset = 0
+                swipeDirection = .none
+                isSwipeTriggered = false
+            }
+        }
         .taskSwipeGesture(
             isActive: true,
             isEditing: isCurrentlyEditing,
-            isDragging: false,
+            isDragging: $isDragging,
             swipeOffset: $swipeOffset,
             swipeDirection: $swipeDirection,
             isTriggered: $isSwipeTriggered,