← All repositories

listless

A simple list app for Apple platforms

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


commit 554b423b2765660f82247aa6d88f7ec9fecb9b81
parent 82da65eebdf0472f3a296bd4356b40ede4575f92
Author: Michael Camilleri <[email protected]>
Date:   Mon,  9 Feb 2026 17:47:15 +0900

Fix row alignment issues on iOS

Co-Authored-By: Claude 4.5 Sonnet <[email protected]>

Diffstat

MListless.xcodeproj/project.pbxproj | 57+++++++++++++++++++++++++--------------------------------
MListless.xcodeproj/xcshareddata/xcschemes/Listless iOS.xcscheme | 16++++++++++++----
MListless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme | 14++++++++++----
RListless/Views/TaskRowView.swift -> ListlessMac/Views/TaskRowView.swift | 42++++++++++++++++--------------------------
MListlessiOS/Info.plist | 4++++
MListlessiOS/ListlessiOSApp.swift | 11+++++++++--
DListlessiOS/Views/ClickableTextField.swift | 35-----------------------------------
AListlessiOS/Views/TaskRowView.swift | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

8 files changed, 257 insertions(+), 103 deletions(-)

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

@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 63;
+	objectVersion = 77;
 	objects = {
 
 /* Begin PBXBuildFile section */
@@ -14,24 +14,23 @@
 		1AA328A921EF8A7FDD03119A /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B048B19C5219862BBED2E7 /* TestHelpers.swift */; };
 		269B93D5543770B464DFB37A /* TaskStoreOrderingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A2DDCE24E54ABCCFBBD4C /* TaskStoreOrderingTests.swift */; };
 		3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
+		3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06485DBE35B60868E14202A /* TaskRowView.swift */; };
 		42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
 		5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
 		614FCCA450EC0BFFD8B40640 /* ListlessMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */; };
 		69D1909CB6D2368CF90065C7 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDF292392702661DBB94D06 /* ColorExtensions.swift */; };
 		6C252050E62AED3A0A684EBF /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; };
+		7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */; };
 		83378A0575640417ED41FC25 /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2BB7ABCABA4EB67180A200 /* ClickableTextField.swift */; };
 		8E08F4C82D6F9E67667CB20A /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
 		8E680EC18B0339931E785F0C /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */; };
-		9041B7CED5298439BF7DC2C1 /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007D500D2EFF3E66B780ADE0 /* TaskRowView.swift */; };
 		91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
 		96617677059FABDBB80D642B /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
-		978DABFA617B6EBD6FA179CD /* ClickableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A08C589AB65ED2CB26A092 /* ClickableTextField.swift */; };
 		99977BFA37FBAAA49AF6B71E /* TaskStoreEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */; };
 		A0AA8FD4C542E9AEB2437BC2 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
 		A8D13DD6196772ECA146CF2A /* PlatformScrollIndicatorsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D74AE2501F35974F57D21F /* PlatformScrollIndicatorsModifier.swift */; };
 		C1BD61D6DFA687E9CB9ACA60 /* TaskRowDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */; };
 		C1FE091454864C4BBBBEB077 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB8A3BEB346267B30B4675F /* TaskItem.swift */; };
-		C2400278D5F6F79C85A68897 /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007D500D2EFF3E66B780ADE0 /* TaskRowView.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 */; };
@@ -55,23 +54,23 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
-		007D500D2EFF3E66B780ADE0 /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
 		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>"; };
-		126108860D7878DDC3BECC4B /* Listless iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		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>"; };
+		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>"; };
 		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>"; };
 		48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
-		4FC64B9F9370041BEDBD1E14 /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; 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>"; };
-		74255E6B6C40899E9B17D927 /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; 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>"; };
 		81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
@@ -79,21 +78,21 @@
 		917597025B3D5D18E33982D3 /* ColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensions.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; lastKnownFileType = text; path = .gitkeep; sourceTree = "<group>"; };
+		944BAE054AAC1B9C4FC954F9 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = "<group>"; };
 		967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreTests.swift; sourceTree = "<group>"; };
-		97A08C589AB65ED2CB26A092 /* ClickableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickableTextField.swift; sourceTree = "<group>"; };
 		9B2BBE01D99CDA278BCB9F49 /* TaskStoreEdgeCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStoreEdgeCaseTests.swift; sourceTree = "<group>"; };
 		9FBEB58DD41817F09B0EB9F0 /* Listless.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Listless.xcdatamodel; 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>"; };
 		BBDF292392702661DBB94D06 /* ColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensions.swift; sourceTree = "<group>"; };
 		C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
-		C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Listless iOS Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
-		C9B14DC786A336008AAB78EE /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = "<group>"; };
-		D10B5491A53E77C80F8F75CD /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = "<group>"; };
-		D123BB181208FC825777B0A7 /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; 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>"; };
+		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>"; };
 		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>"; };
 		EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
 		FBB8A3BEB346267B30B4675F /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -105,7 +104,6 @@
 				D123BB181208FC825777B0A7 /* .gitkeep */,
 				0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */,
 				537A913AC421BAEF60D26D9C /* TaskListView.swift */,
-				007D500D2EFF3E66B780ADE0 /* TaskRowView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -134,12 +132,12 @@
 		58F917D865E0BDF4EF282306 /* Views */ = {
 			isa = PBXGroup;
 			children = (
-				97A08C589AB65ED2CB26A092 /* ClickableTextField.swift */,
 				82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */,
 				48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */,
 				81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */,
 				93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */,
 				1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */,
+				199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -184,6 +182,7 @@
 				EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */,
 				431B0EA72D590AF0CC868515 /* TaskListView+Toolbar.swift */,
 				138612F350ECE29526F689B9 /* TaskRowDragGesture.swift */,
+				E06485DBE35B60868E14202A /* TaskRowView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -306,7 +305,7 @@
 			isa = PBXProject;
 			attributes = {
 				BuildIndependentTargetsInParallel = YES;
-				LastUpgradeCheck = 2620;
+				LastUpgradeCheck = 1430;
 				TargetAttributes = {
 					0FB4F07A37999BBC6DFE4DBB = {
 						DevelopmentTeam = 7TD7PZBNXP;
@@ -332,6 +331,7 @@
 			);
 			mainGroup = ED4862258A8A70025EE14416;
 			minimizedProjectReferenceProxies = 1;
+			preferredProjectObjectVersion = 77;
 			projectDirPath = "";
 			projectRoot = "";
 			targets = (
@@ -360,7 +360,7 @@
 				074A81D9DAF58E8E088CBC89 /* TaskListView+Toolbar.swift in Sources */,
 				8E08F4C82D6F9E67667CB20A /* TaskListView.swift in Sources */,
 				C1BD61D6DFA687E9CB9ACA60 /* TaskRowDragGesture.swift in Sources */,
-				C2400278D5F6F79C85A68897 /* TaskRowView.swift in Sources */,
+				3D1F551A03B97ECF4E3DC8B0 /* TaskRowView.swift in Sources */,
 				3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -369,7 +369,6 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				978DABFA617B6EBD6FA179CD /* ClickableTextField.swift in Sources */,
 				69D1909CB6D2368CF90065C7 /* ColorExtensions.swift in Sources */,
 				FEC96DAA2BF8BB5D6D8504EB /* HoverCursorModifier.swift in Sources */,
 				6C252050E62AED3A0A684EBF /* KeyboardNavigationModifier.swift in Sources */,
@@ -382,7 +381,7 @@
 				D9DA553485AD0CA28D5BE0C8 /* TaskListView+Toolbar.swift in Sources */,
 				42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */,
 				D6B3DBDD6A3F0A6E166CFFD5 /* TaskRowDragGesture.swift in Sources */,
-				9041B7CED5298439BF7DC2C1 /* TaskRowView.swift in Sources */,
+				7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */,
 				91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -417,7 +416,7 @@
 				CODE_SIGN_ENTITLEMENTS = ListlessMac/Listless.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				DEAD_CODE_STRIPPING = YES;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				INFOPLIST_FILE = ListlessMac/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -435,7 +434,7 @@
 				CODE_SIGN_ENTITLEMENTS = ListlessMac/Listless.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				DEAD_CODE_STRIPPING = YES;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				INFOPLIST_FILE = ListlessMac/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -450,7 +449,6 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -481,12 +479,9 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				DEAD_CODE_STRIPPING = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				ENABLE_USER_SCRIPT_SANDBOXING = YES;
 				GCC_C_LANGUAGE_STANDARD = gnu11;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -500,7 +495,6 @@
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
-				STRING_CATALOG_GENERATE_SYMBOLS = YES;
 				SWIFT_COMPILATION_MODE = wholemodule;
 				SWIFT_OPTIMIZATION_LEVEL = "-O";
 				SWIFT_VERSION = 6;
@@ -512,6 +506,7 @@
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				GENERATE_INFOPLIST_FILE = YES;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -529,7 +524,6 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -560,12 +554,9 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				DEAD_CODE_STRIPPING = YES;
 				DEBUG_INFORMATION_FORMAT = dwarf;
-				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
-				ENABLE_USER_SCRIPT_SANDBOXING = YES;
 				GCC_C_LANGUAGE_STANDARD = gnu11;
 				GCC_DYNAMIC_NO_PIC = NO;
 				GCC_NO_COMMON_BLOCKS = YES;
@@ -586,7 +577,6 @@
 				MTL_FAST_MATH = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
-				STRING_CATALOG_GENERATE_SYMBOLS = YES;
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
 				SWIFT_VERSION = 6;
@@ -600,6 +590,7 @@
 				CODE_SIGN_ENTITLEMENTS = ListlessiOS/Listless.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				INFOPLIST_FILE = ListlessiOS/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -616,6 +607,7 @@
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				GENERATE_INFOPLIST_FILE = YES;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -636,6 +628,7 @@
 				CODE_SIGN_ENTITLEMENTS = ListlessiOS/Listless.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7TD7PZBNXP;
 				INFOPLIST_FILE = ListlessiOS/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",

diff --git a/Listless.xcodeproj/xcshareddata/xcschemes/Listless iOS.xcscheme b/Listless.xcodeproj/xcshareddata/xcschemes/Listless iOS.xcscheme

@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "2620"
-   version = "1.3">
+   LastUpgradeVersion = "1430"
+   version = "1.7">
    <BuildAction
       parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
+      buildImplicitDependencies = "YES"
+      runPostActionsOnFailure = "NO">
       <BuildActionEntries>
          <BuildActionEntry
             buildForTesting = "YES"
@@ -26,7 +27,8 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      onlyGenerateCoverageForSpecifiedTargets = "NO">
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -50,6 +52,8 @@
             </BuildableReference>
          </TestableReference>
       </Testables>
+      <CommandLineArguments>
+      </CommandLineArguments>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -71,6 +75,8 @@
             ReferencedContainer = "container:Listless.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
+      <CommandLineArguments>
+      </CommandLineArguments>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
@@ -88,6 +94,8 @@
             ReferencedContainer = "container:Listless.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
+      <CommandLineArguments>
+      </CommandLineArguments>
    </ProfileAction>
    <AnalyzeAction
       buildConfiguration = "Debug">

diff --git a/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme b/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme

@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "2620"
-   version = "1.3">
+   LastUpgradeVersion = "1430"
+   version = "1.7">
    <BuildAction
       parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
+      buildImplicitDependencies = "YES"
+      runPostActionsOnFailure = "NO">
       <BuildActionEntries>
          <BuildActionEntry
             buildForTesting = "YES"
@@ -26,7 +27,8 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      onlyGenerateCoverageForSpecifiedTargets = "NO">
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -59,6 +61,8 @@
             ReferencedContainer = "container:Listless.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
+      <CommandLineArguments>
+      </CommandLineArguments>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
@@ -76,6 +80,8 @@
             ReferencedContainer = "container:Listless.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
+      <CommandLineArguments>
+      </CommandLineArguments>
    </ProfileAction>
    <AnalyzeAction
       buildConfiguration = "Debug">

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

@@ -17,6 +17,7 @@ struct TaskRowView: View {
 
     @State private var editingTitle: String = ""
     @State private var isCurrentlyEditing: Bool = false
+    @State private var cachedAccentColor: Color = .clear
 
     private let horizontalPadding: CGFloat = 16
     private let checkboxTextSpacing: CGFloat = 12
@@ -26,7 +27,7 @@ struct TaskRowView: View {
         horizontalPadding + checkboxSize + checkboxTextSpacing
     }
 
-    private var accentColor: Color {
+    private func computeAccentColor() -> Color {
         guard !task.isCompleted else { return .clear }
         guard totalTasks > 1 else { return Color(hue: 0.98, saturation: 0.85, brightness: 1.0) }
 
@@ -137,7 +138,7 @@ struct TaskRowView: View {
         .overlay(alignment: .leading) {
             // Colored accent bar on the left edge
             Rectangle()
-                .fill(accentColor)
+                .fill(cachedAccentColor)
                 .frame(width: 4)
                 .padding(.vertical, 1)
         }
@@ -180,27 +181,24 @@ struct TaskRowView: View {
                 editingTitle = newValue
             }
         }
+        .onChange(of: "\(index)-\(totalTasks)") { _, _ in
+            cachedAccentColor = computeAccentColor()
+        }
         .onAppear {
-            // Initialize editingTitle
+            // Initialize editingTitle and cache accent color (computed once)
             editingTitle = task.title
+            cachedAccentColor = computeAccentColor()
         }
     }
 
+    @ViewBuilder
     private var selectionBackground: some View {
-        Group {
-            if isSelected {
-                RoundedRectangle(cornerRadius: 6, style: .continuous)
-                    .fill(selectionFill)
-            } else {
-                Color.clear
-            }
+        if isSelected {
+            RoundedRectangle(cornerRadius: 6, style: .continuous)
+                .fill(Color.accentColor.opacity(0.2))
         }
     }
 
-    private var selectionFill: Color {
-        Color.accentColor.opacity(0.2)
-    }
-
     private func cutToPasteboard() {
         copyToPasteboard()
         onDelete(task)
@@ -209,21 +207,13 @@ struct TaskRowView: View {
     private func copyToPasteboard() {
         let text = isEditing ? editingTitle : task.title
         guard !text.isEmpty else { return }
-        #if os(macOS)
-            let pasteboard = NSPasteboard.general
-            pasteboard.clearContents()
-            pasteboard.setString(text, forType: .string)
-        #else
-            UIPasteboard.general.string = text
-        #endif
+        let pasteboard = NSPasteboard.general
+        pasteboard.clearContents()
+        pasteboard.setString(text, forType: .string)
     }
 
     private func pasteFromPasteboard() {
-        #if os(macOS)
-            guard let string = NSPasteboard.general.string(forType: .string) else { return }
-        #else
-            guard let string = UIPasteboard.general.string else { return }
-        #endif
+        guard let string = NSPasteboard.general.string(forType: .string) else { return }
         if isEditing {
             editingTitle = string
         }

diff --git a/ListlessiOS/Info.plist b/ListlessiOS/Info.plist

@@ -22,6 +22,10 @@
 	<string>1</string>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
+	<key>UIBackgroundModes</key>
+	<array>
+		<string>remote-notification</string>
+	</array>
 	<key>UILaunchScreen</key>
 	<dict/>
 	<key>UISupportedInterfaceOrientations</key>

diff --git a/ListlessiOS/ListlessiOSApp.swift b/ListlessiOS/ListlessiOSApp.swift

@@ -6,8 +6,15 @@ struct ListlessiOSApp: App {
 
     var body: some Scene {
         WindowGroup {
-            TaskListView(store: TaskStore(persistenceController: persistenceController))
-                .environment(\.managedObjectContext, persistenceController.viewContext)
+            NavigationStack {
+                TaskListView(store: TaskStore(persistenceController: persistenceController))
+                    .navigationTitle("Tasks")
+                    .navigationBarTitleDisplayMode(.large)
+                    .safeAreaInset(edge: .top) {
+                        Color.clear.frame(height: 8)
+                    }
+                    .environment(\.managedObjectContext, persistenceController.viewContext)
+            }
         }
     }
 }

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

@@ -1,35 +0,0 @@
-import SwiftUI
-
-/// iOS version - simple TextField wrapper
-struct ClickableTextField: View {
-    @Binding var text: String
-    let isCompleted: Bool
-    let onEditingChanged: (Bool, _ shouldCreateNewTask: Bool) -> Void
-
-    @FocusState private var isFocused: Bool
-    @State private var submittedViaReturn = false
-
-    var body: some View {
-        TextField("New task", text: $text, axis: .vertical)
-            .textFieldStyle(.plain)
-            .font(.body)
-            .lineLimit(1...5)
-            .focused($isFocused)
-            .onSubmit {
-                // Return key pressed - mark for new task creation
-                submittedViaReturn = true
-                isFocused = false
-            }
-            .disabled(isCompleted)
-            .onChange(of: isFocused) { _, newValue in
-                if newValue {
-                    // Focus gained
-                    onEditingChanged(true, false)
-                } else {
-                    // Focus lost - check if it was via Return key
-                    onEditingChanged(false, submittedViaReturn)
-                    submittedViaReturn = false
-                }
-            }
-    }
-}

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

@@ -0,0 +1,181 @@
+import SwiftUI
+
+struct TaskRowView: View {
+    let task: TaskItem
+    let taskID: UUID
+    let index: Int
+    let totalTasks: Int
+    let isSelected: Bool
+    let isEditing: Bool
+    let onToggle: (TaskItem) -> Void
+    let onTitleChange: (TaskItem, String) -> Void
+    let onDelete: (TaskItem) -> Void
+    let onSelect: (UUID) -> Void
+    let onStartEdit: (UUID) -> Void
+    let onEndEdit: (UUID, _ shouldCreateNewTask: Bool) -> Void
+    @FocusState.Binding var focusedField: TaskListView.FocusField?
+
+    @State private var editingTitle: String = ""
+    @State private var isCurrentlyEditing: Bool = false
+    @State private var submittedViaReturn = false
+
+    private let horizontalPadding: CGFloat = 16
+    private let checkboxTextSpacing: CGFloat = 12
+    @ScaledMetric private var checkboxSize: CGFloat = 20
+
+    private var dividerInset: CGFloat {
+        horizontalPadding + checkboxSize + checkboxTextSpacing
+    }
+
+    init(
+        task: TaskItem,
+        taskID: UUID,
+        index: Int = 0,
+        totalTasks: Int = 1,
+        isSelected: Bool,
+        isEditing: Bool = false,
+        focusedField: FocusState<TaskListView.FocusField?>.Binding,
+        onToggle: @escaping (TaskItem) -> Void,
+        onTitleChange: @escaping (TaskItem, String) -> Void,
+        onDelete: @escaping (TaskItem) -> Void,
+        onSelect: @escaping (UUID) -> Void,
+        onStartEdit: @escaping (UUID) -> Void = { _ in },
+        onEndEdit: @escaping (UUID, _ shouldCreateNewTask: Bool) -> Void = { _, _ in }
+    ) {
+        self.task = task
+        self.taskID = taskID
+        self.index = index
+        self.totalTasks = totalTasks
+        self.isSelected = isSelected
+        self.isEditing = isEditing
+        self.onToggle = onToggle
+        self.onTitleChange = onTitleChange
+        self.onDelete = onDelete
+        self.onSelect = onSelect
+        self.onStartEdit = onStartEdit
+        self.onEndEdit = onEndEdit
+        _focusedField = focusedField
+    }
+
+    var body: some View {
+        HStack(alignment: .center, spacing: 12) {
+            Button {
+                onToggle(task)
+            } label: {
+                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
+                    .foregroundStyle(task.isCompleted ? .secondary : .primary)
+                    .font(.system(size: 17))
+                    .fontWeight(.thin)
+            }
+            .buttonStyle(.borderless)
+            .accessibilityIdentifier("task-checkbox")
+            .accessibilityValue(task.isCompleted ? "checkmark.circle.fill" : "circle")
+
+            TextField("New task", text: $editingTitle, axis: .vertical)
+                .textFieldStyle(.plain)
+                .font(.body)
+                .lineLimit(1...5)
+                .focused($focusedField, equals: .task(taskID))
+                .onSubmit {
+                    // Return key pressed - mark for new task creation
+                    submittedViaReturn = true
+                    focusedField = nil
+                }
+                .disabled(task.isCompleted)
+                .frame(maxWidth: .infinity, alignment: .leading)
+                .accessibilityIdentifier(
+                    isCurrentlyEditing ? "task-textfield" : "task-text-\(task.title)")
+        }
+        .padding(.vertical, 8)
+        .padding(.horizontal, 16)
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .contentShape(Rectangle())
+        .onTapGesture {
+            onSelect(taskID)
+        }
+        .background(selectionBackground)
+        .overlay(alignment: .bottom) {
+            // Hairline border between rows, inset to align with text
+            // Only show for active (non-completed) tasks
+            if !task.isCompleted {
+                Rectangle()
+                    .fill(.separator)
+                    .frame(height: 0.5)
+                    .padding(.leading, dividerInset)
+            }
+        }
+        .contextMenu {
+            Button(task.isCompleted ? "Mark as Incomplete" : "Mark as Complete") {
+                onToggle(task)
+            }
+            Divider()
+            Button("Cut") {
+                cutToPasteboard()
+            }
+            Button("Copy") {
+                copyToPasteboard()
+            }
+            Button("Paste") {
+                pasteFromPasteboard()
+            }
+            Divider()
+            Button("Delete", role: .destructive) {
+                onDelete(task)
+            }
+        }
+        .onChange(of: editingTitle) {
+            guard !task.isCompleted else { return }
+            onTitleChange(task, editingTitle)
+        }
+        .onChange(of: task.title) { _, newValue in
+            // Keep editingTitle in sync with task.title when not editing
+            if !isCurrentlyEditing {
+                editingTitle = newValue
+            }
+        }
+        .onChange(of: focusedField) { _, newValue in
+            let isFocused = newValue == .task(taskID)
+            if isFocused {
+                // Focus gained
+                isCurrentlyEditing = true
+                onStartEdit(taskID)
+            } else if isCurrentlyEditing {
+                // Focus lost - check if it was via Return key
+                isCurrentlyEditing = false
+                onEndEdit(taskID, submittedViaReturn)
+                submittedViaReturn = false
+            }
+        }
+        .onAppear {
+            // Initialize editingTitle
+            editingTitle = task.title
+        }
+    }
+
+    @ViewBuilder
+    private var selectionBackground: some View {
+        if isSelected {
+            RoundedRectangle(cornerRadius: 6, style: .continuous)
+                .fill(Color.accentColor.opacity(0.2))
+        }
+    }
+
+    private func cutToPasteboard() {
+        copyToPasteboard()
+        onDelete(task)
+    }
+
+    private func copyToPasteboard() {
+        let text = isEditing ? editingTitle : task.title
+        guard !text.isEmpty else { return }
+        UIPasteboard.general.string = text
+    }
+
+    private func pasteFromPasteboard() {
+        guard let string = UIPasteboard.general.string else { return }
+        if isEditing {
+            editingTitle = string
+        }
+        onTitleChange(task, string)
+    }
+}