From b87622d057d5cc0c08e48c3add539ff0aa47f9bc Mon Sep 17 00:00:00 2001 From: Takeshi Shimada Date: Mon, 27 Oct 2025 22:13:32 +0900 Subject: [PATCH 1/2] feat: Add task naming support for improved debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement task naming feature leveraging Swift 6.2's SE-0469 task naming capability. This enhancement allows developers to assign human-readable names to tasks created with .run effects, significantly improving observability during debugging and profiling. ## Changes ### Core Implementation **ActionTask.swift:** - Add `name: String?` parameter to `Operation.run` case - Add `name` parameter to `.run(name:operation:)` factory method (optional, default nil) - Preserve `name` through all configuration methods (`.catch`, `.cancellable`, `.priority`) - Update documentation with task naming examples **TaskManager.swift:** - Add `name: String?` parameter to `executeTask` method - Pass `name` to Swift's `Task(name:priority:)` initializer - Update documentation and examples **Store.swift:** - Add `name` parameter to `executeRunTask` helper method - Thread `name` through to TaskManager.executeTask - Extract `name` from ActionTask operation ### Tests **ActionTaskTests.swift:** - Add 6 new tests for task naming feature: - `run_withName()` - Verify name is set - `run_withoutName()` - Verify backward compatibility - `run_namePreservedAfterCatch()` - Name preserved through `.catch` - `run_namePreservedAfterCancellable()` - Name preserved through `.cancellable` - `run_namePreservedAfterPriority()` - Name preserved through `.priority` - `run_namePreservedThroughChaining()` - Name preserved through complete chain **Test Pattern Updates:** - Update all test pattern matches to account for new `name` field in `Operation.run` - 299 tests pass (293 existing + 6 new) ## Benefits - ✅ **Improved Debugging**: Tasks are easily identifiable in Xcode, Instruments, and swift-inspect - ✅ **Better Observability**: Human-readable task names in debugging tools - ✅ **Backward Compatible**: `name` parameter is optional with `nil` default - ✅ **Minimal Impact**: No breaking changes to existing code ## Example Usage ```swift // Named task for better debugging return .run(name: "🔄 Fetch user profile") { state in let profile = try await api.fetchProfile() state.profile = profile } // Dynamic naming with interpolation return .run(name: "Load user \(userId)") { state in let user = try await api.fetchUser(userId) state.user = user } // Name preserved through chaining return .run(name: "Critical operation") { state in try await performOperation() } .cancellable(id: "op", cancelInFlight: true) .priority(.high) .catch { error, state in state.error = error } ``` ## Requirements - Swift 6.2+ (for Task naming API) - Fully backward compatible with existing code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Sources/Flow/Store/ActionTask.swift | 20 +++- Sources/Flow/Store/Store.swift | 5 +- Sources/Flow/Store/TaskManager.swift | 5 +- .../ActionHandler/ActionHandlerTests.swift | 8 +- .../ActionHandler/ActionProcessorTests.swift | 8 +- .../Store/ActionTaskCancellableTests.swift | 20 ++-- .../Store/ActionTaskPriorityTests.swift | 20 ++-- Tests/UnitTests/Store/ActionTaskTests.swift | 112 ++++++++++++++++-- 8 files changed, 151 insertions(+), 47 deletions(-) diff --git a/Sources/Flow/Store/ActionTask.swift b/Sources/Flow/Store/ActionTask.swift index 8be82dc..e23bded 100644 --- a/Sources/Flow/Store/ActionTask.swift +++ b/Sources/Flow/Store/ActionTask.swift @@ -200,6 +200,7 @@ public struct ActionTask { /// Execute an asynchronous operation that returns a result case run( id: String, + name: String?, operation: @MainActor (State) async throws -> ActionResult, onError: (@MainActor (Error, State) -> Void)?, cancelInFlight: Bool, @@ -285,8 +286,8 @@ extension ActionTask { /// return .created(id: outcome.id) /// } /// - /// // Make it cancellable with ID - /// return .run { state in + /// // Named task for better debugging + /// return .run(name: "🔄 Fetch user data") { state in /// let data = try await fetch() /// state.data = data /// return .fetched @@ -294,15 +295,19 @@ extension ActionTask { /// .cancellable(id: "fetch", cancelInFlight: true) /// ``` /// - /// - Parameter operation: The async operation to execute, receiving mutable state and returning a result + /// - Parameters: + /// - name: Optional human-readable name for the task (useful for debugging and profiling) + /// - operation: The async operation to execute, receiving mutable state and returning a result /// - Returns: A new `ActionTask` that will execute the operation public static func run( + name: String? = nil, operation: @escaping @MainActor (State) async throws -> ActionResult ) -> ActionTask { let taskId = TaskIdGenerator.generate() return ActionTask( operation: .run( id: taskId, + name: name, operation: operation, onError: nil, cancelInFlight: false, @@ -542,10 +547,11 @@ extension ActionTask { /// - Returns: A new `ActionTask` with the error handler attached public func `catch`(_ handler: @escaping @MainActor (Error, State) -> Void) -> ActionTask { switch operation { - case .run(let id, let op, _, let cancelInFlight, let priority): + case .run(let id, let name, let op, _, let cancelInFlight, let priority): return ActionTask( operation: .run( id: id, + name: name, operation: op, onError: handler, cancelInFlight: cancelInFlight, @@ -590,11 +596,12 @@ extension ActionTask { cancelInFlight: Bool = false ) -> ActionTask { switch operation { - case .run(_, let op, let onError, _, let priority): + case .run(_, let name, let op, let onError, _, let priority): let stringId = id.taskIdString return ActionTask( operation: .run( id: stringId, + name: name, operation: op, onError: onError, cancelInFlight: cancelInFlight, @@ -641,10 +648,11 @@ extension ActionTask { /// - Returns: A new `ActionTask` with the specified priority public func priority(_ priority: TaskPriority) -> ActionTask { switch operation { - case .run(let id, let op, let onError, let cancelInFlight, _): + case .run(let id, let name, let op, let onError, let cancelInFlight, _): return ActionTask( operation: .run( id: id, + name: name, operation: op, onError: onError, cancelInFlight: cancelInFlight, diff --git a/Sources/Flow/Store/Store.swift b/Sources/Flow/Store/Store.swift index 7850083..ced29be 100644 --- a/Sources/Flow/Store/Store.swift +++ b/Sources/Flow/Store/Store.swift @@ -206,6 +206,7 @@ public final class Store { /// managing task cancellation, and error handling. private func executeRunTask( id: String, + name: String?, operation: @escaping @MainActor (F.State) async throws -> F.ActionResult, onError: (@MainActor (Error, F.State) -> Void)?, cancelInFlight: Bool, @@ -220,6 +221,7 @@ public final class Store { let runningTask = taskManager.executeTask( id: id, + name: name, operation: { @MainActor [weak self] in guard let self else { throw StoreError.deallocated @@ -310,9 +312,10 @@ public final class Store { case .just(let result): return result - case .run(let id, let operation, let onError, let cancelInFlight, let priority): + case .run(let id, let name, let operation, let onError, let cancelInFlight, let priority): return try await executeRunTask( id: id, + name: name, operation: operation, onError: onError, cancelInFlight: cancelInFlight, diff --git a/Sources/Flow/Store/TaskManager.swift b/Sources/Flow/Store/TaskManager.swift index edab827..b1952e9 100644 --- a/Sources/Flow/Store/TaskManager.swift +++ b/Sources/Flow/Store/TaskManager.swift @@ -114,6 +114,7 @@ public final class TaskManager { /// /// - Parameters: /// - id: Unique identifier for the task (string representation) + /// - name: Optional human-readable name for the task (useful for debugging and profiling) /// - operation: The asynchronous operation to execute /// - onError: Optional error handler called if the operation throws /// - priority: Optional task priority (defaults to nil, using system default) @@ -123,6 +124,7 @@ public final class TaskManager { /// ```swift /// let task = taskManager.executeTask( /// id: "loadProfile", + /// name: "🔄 Load user profile", /// operation: { /// let profile = try await api.fetchProfile() /// await store.send(.profileLoaded(profile)) @@ -140,6 +142,7 @@ public final class TaskManager { @discardableResult public func executeTask( id: String, + name: String? = nil, operation: @escaping () async throws -> Void, onError: ((Error) async -> Void)?, priority: TaskPriority? = nil @@ -152,7 +155,7 @@ public final class TaskManager { // Use [weak self] to prevent retain cycle (TaskManager ← runningTasks ← Task) // Ensures deinit runs when Store deallocates, cancelling all tasks - let task = Task(priority: priority) { @MainActor [weak self] in + let task = Task(name: name, priority: priority) { @MainActor [weak self] in guard let self else { return } // Defer ensures cleanup happens exactly once, regardless of how the task completes diff --git a/Tests/UnitTests/ActionHandler/ActionHandlerTests.swift b/Tests/UnitTests/ActionHandler/ActionHandlerTests.swift index 53a0d9c..fab3c6a 100644 --- a/Tests/UnitTests/ActionHandler/ActionHandlerTests.swift +++ b/Tests/UnitTests/ActionHandler/ActionHandlerTests.swift @@ -109,7 +109,7 @@ import Testing // THEN: Should return run task #expect(state.isLoading) - if case .run(let id, _, _, _, _) = task.operation { + if case .run(let id, _, _, _, _, _) = task.operation { #expect(id == "test") } else { Issue.record("Expected run task") @@ -286,7 +286,7 @@ import Testing // THEN: Task should be transformed #expect(state.count == 1) - if case .run(let id, _, _, _, _) = task.operation { + if case .run(let id, _, _, _, _, _) = task.operation { #expect(id == "transformed") } else { Issue.record("Expected run task") @@ -301,7 +301,7 @@ import Testing } .transform { task in switch task.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): return .cancel(id: id) default: return task @@ -466,7 +466,7 @@ import Testing let task = await sut.handle(action: .asyncOp, state: state) #expect(state.isLoading) - if case .run(let id, _, _, _, _) = task.operation { + if case .run(let id, _, _, _, _, _) = task.operation { #expect(id == "complex") } diff --git a/Tests/UnitTests/ActionHandler/ActionProcessorTests.swift b/Tests/UnitTests/ActionHandler/ActionProcessorTests.swift index c4462f5..8812b64 100644 --- a/Tests/UnitTests/ActionHandler/ActionProcessorTests.swift +++ b/Tests/UnitTests/ActionHandler/ActionProcessorTests.swift @@ -122,7 +122,7 @@ import Testing // THEN: Should return run task #expect(state.isLoading) - if case .run(let id, _, _, _, _) = task.operation { + if case .run(let id, _, _, _, _, _) = task.operation { #expect(id == "test-task") } else { Issue.record("Expected run task") @@ -282,7 +282,7 @@ import Testing // THEN: Task should be transformed #expect(state.count == 1) - if case .run(let id, _, _, _, _) = task.operation { + if case .run(let id, _, _, _, _, _) = task.operation { #expect(id == "transformed") } else { Issue.record("Expected run task") @@ -297,7 +297,7 @@ import Testing } .transform { task in switch task.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): return .cancel(id: id) default: return task @@ -398,7 +398,7 @@ import Testing #expect(state.count == 15) #expect(middlewareExecuted) #expect(state.errorMessage == nil) - if case .run(let id, _, _, _, _) = task.operation { + if case .run(let id, _, _, _, _, _) = task.operation { #expect(id == "main-task") } } diff --git a/Tests/UnitTests/Store/ActionTaskCancellableTests.swift b/Tests/UnitTests/Store/ActionTaskCancellableTests.swift index 41d0c17..27fd5b4 100644 --- a/Tests/UnitTests/Store/ActionTaskCancellableTests.swift +++ b/Tests/UnitTests/Store/ActionTaskCancellableTests.swift @@ -32,7 +32,7 @@ import Testing // THEN: Should have the specified ID and cancelInFlight = false switch sut.operation { - case .run(let id, _, _, let cancelInFlight, _): + case .run(let id, _, _, _, let cancelInFlight, _): #expect(id == "search") #expect(!cancelInFlight) default: @@ -47,7 +47,7 @@ import Testing // THEN: Should have cancelInFlight = true switch sut.operation { - case .run(let id, _, _, let cancelInFlight, _): + case .run(let id, _, _, _, let cancelInFlight, _): #expect(id == "search") #expect(cancelInFlight) default: @@ -62,7 +62,7 @@ import Testing // THEN: Should use the new ID from cancellable switch sut.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): #expect(id == "new-id") default: Issue.record("Expected run task") @@ -105,7 +105,7 @@ import Testing // THEN: Should have both ID, cancelInFlight, and error handler switch sut.operation { - case .run(let id, _, let onError, let cancelInFlight, _): + case .run(let id, _, _, let onError, let cancelInFlight, _): #expect(id == "search") #expect(cancelInFlight) #expect(onError != nil) @@ -122,7 +122,7 @@ import Testing // THEN: Should preserve error handler and set cancellable switch sut.operation { - case .run(let id, _, let onError, let cancelInFlight, _): + case .run(let id, _, _, let onError, let cancelInFlight, _): #expect(id == "search") #expect(cancelInFlight) #expect(onError != nil) @@ -138,7 +138,7 @@ import Testing // THEN: Should convert Int to String switch sut.operation { - case .run(let id, _, _, let cancelInFlight, _): + case .run(let id, _, _, _, let cancelInFlight, _): #expect(id == "42") #expect(cancelInFlight) default: @@ -154,7 +154,7 @@ import Testing // THEN: Should convert UUID to String switch sut.operation { - case .run(let id, _, _, let cancelInFlight, _): + case .run(let id, _, _, _, let cancelInFlight, _): #expect(id == uuid.uuidString) #expect(cancelInFlight) default: @@ -174,7 +174,7 @@ import Testing // THEN: Should use enum raw value switch sut.operation { - case .run(let id, _, _, let cancelInFlight, _): + case .run(let id, _, _, _, let cancelInFlight, _): #expect(id == "search") #expect(cancelInFlight) default: @@ -189,7 +189,7 @@ import Testing // THEN: cancelInFlight should default to false switch sut.operation { - case .run(_, _, _, let cancelInFlight, _): + case .run(_, _, _, _, let cancelInFlight, _): #expect(!cancelInFlight) default: Issue.record("Expected run task") @@ -204,7 +204,7 @@ import Testing // THEN: Last call should override switch sut.operation { - case .run(let id, _, _, let cancelInFlight, _): + case .run(let id, _, _, _, let cancelInFlight, _): #expect(id == "second") #expect(cancelInFlight) default: diff --git a/Tests/UnitTests/Store/ActionTaskPriorityTests.swift b/Tests/UnitTests/Store/ActionTaskPriorityTests.swift index 1f9eb65..55d6d63 100644 --- a/Tests/UnitTests/Store/ActionTaskPriorityTests.swift +++ b/Tests/UnitTests/Store/ActionTaskPriorityTests.swift @@ -33,7 +33,7 @@ import Testing // THEN: Should have high priority switch result.operation { - case .run(_, _, _, _, let priority): + case .run(_, _, _, _, _, let priority): #expect(priority == .high) default: Issue.record("Expected run task") @@ -51,7 +51,7 @@ import Testing // THEN: Should have low priority switch result.operation { - case .run(_, _, _, _, let priority): + case .run(_, _, _, _, _, let priority): #expect(priority == .low) default: Issue.record("Expected run task") @@ -69,7 +69,7 @@ import Testing // THEN: Should have background priority switch result.operation { - case .run(_, _, _, _, let priority): + case .run(_, _, _, _, _, let priority): #expect(priority == .background) default: Issue.record("Expected run task") @@ -87,7 +87,7 @@ import Testing // THEN: Should have userInitiated priority switch result.operation { - case .run(_, _, _, _, let priority): + case .run(_, _, _, _, _, let priority): #expect(priority == .userInitiated) default: Issue.record("Expected run task") @@ -102,7 +102,7 @@ import Testing // THEN: Priority should be nil (system default) switch sut.operation { - case .run(_, _, _, _, let priority): + case .run(_, _, _, _, _, let priority): #expect(priority == nil) default: Issue.record("Expected run task") @@ -125,7 +125,7 @@ import Testing // THEN: Should have both priority and cancellable ID switch result.operation { - case .run(let id, _, _, let cancelInFlight, let priority): + case .run(let id, _, _, _, let cancelInFlight, let priority): #expect(id == "test-task") #expect(cancelInFlight == true) #expect(priority == .high) @@ -150,7 +150,7 @@ import Testing // THEN: Should have both priority and error handler switch result.operation { - case .run(_, _, let onError, _, let priority): + case .run(_, _, _, let onError, _, let priority): #expect(priority == .userInitiated) #expect(onError != nil) default: @@ -175,7 +175,7 @@ import Testing // THEN: Should preserve all configurations switch result.operation { - case .run(let id, _, let onError, let cancelInFlight, let priority): + case .run(let id, _, _, let onError, let cancelInFlight, let priority): #expect(id == "full-chain") #expect(cancelInFlight == false) #expect(priority == .high) @@ -202,7 +202,7 @@ import Testing // THEN: Should preserve all configurations regardless of order switch result.operation { - case .run(let id, _, let onError, _, let priority): + case .run(let id, _, _, let onError, _, let priority): #expect(id == "order-test") #expect(priority == .low) #expect(onError != nil) @@ -225,7 +225,7 @@ import Testing // THEN: Should have the new priority switch result.operation { - case .run(_, _, _, _, let priority): + case .run(_, _, _, _, _, let priority): #expect(priority == .low) default: Issue.record("Expected run task") diff --git a/Tests/UnitTests/Store/ActionTaskTests.swift b/Tests/UnitTests/Store/ActionTaskTests.swift index e5f23bf..c045be9 100644 --- a/Tests/UnitTests/Store/ActionTaskTests.swift +++ b/Tests/UnitTests/Store/ActionTaskTests.swift @@ -72,7 +72,7 @@ import Testing // THEN: Should have run storeTask with correct ID switch sut.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): #expect(id == taskId) default: Issue.record("Expected run task, got different type") @@ -85,7 +85,7 @@ import Testing // THEN: Should have run storeTask with auto-generated ID switch sut.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): #expect(id.hasPrefix("auto-task-"), "ID should have auto-task prefix") #expect(id.count > "auto-task-".count, "ID should have unique suffix") default: @@ -102,11 +102,11 @@ import Testing var id1: String? var id2: String? - if case .run(let id, _, _, _, _) = task1.operation { + if case .run(let id, _, _, _, _, _) = task1.operation { id1 = id } - if case .run(let id, _, _, _, _) = task2.operation { + if case .run(let id, _, _, _, _, _) = task2.operation { id2 = id } @@ -147,7 +147,7 @@ import Testing // THEN: Should accept and store the long ID switch sut.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): #expect(id == longId) #expect(id.count == 1000) default: @@ -165,7 +165,7 @@ import Testing // THEN: Should accept and store the ID with special characters switch sut.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): #expect(id == specialId) default: Issue.record("Expected run task") @@ -333,7 +333,7 @@ import Testing // THEN: Should have run task with error handler switch result.operation { - case .run(let id, _, let onError, _, _): + case .run(let id, _, _, let onError, _, _): #expect(id == "test") #expect(onError != nil, "Error handler should be attached") default: @@ -350,7 +350,7 @@ import Testing // THEN: Should preserve auto-generated ID and attach handler switch result.operation { - case .run(let id, _, let onError, _, _): + case .run(let id, _, _, let onError, _, _): #expect(id.hasPrefix("auto-task-"), "Should preserve auto-generated ID") #expect(onError != nil, "Error handler should be attached") default: @@ -371,7 +371,7 @@ import Testing // THEN: Should have the last error handler switch result.operation { - case .run(_, _, let onError, _, _): + case .run(_, _, _, let onError, _, _): #expect(onError != nil, "Should have error handler") // Note: Can't easily test which handler is attached in unit test // This is tested in integration tests @@ -391,7 +391,7 @@ import Testing // THEN: Should preserve the original task ID switch result.operation { - case .run(let id, _, _, _, _): + case .run(let id, _, _, _, _, _): #expect(id == originalId, "Task ID should be preserved") default: Issue.record("Expected run task") @@ -411,7 +411,7 @@ import Testing // THEN: Should successfully attach the handler switch result.operation { - case .run(let id, _, let onError, _, _): + case .run(let id, _, _, let onError, _, _): #expect(id == "test") #expect(onError != nil) default: @@ -541,4 +541,94 @@ import Testing Issue.record("Expected cancels task, got different type") } } + + // MARK: - Task Naming + + @Test func run_withName() { + // GIVEN: A run task with a name + let sut: ActionTask = .run(name: "Fetch user data") { _ in } + + // THEN: Should have the specified name + switch sut.operation { + case .run(_, let name, _, _, _, _): + #expect(name == "Fetch user data") + default: + Issue.record("Expected run task") + } + } + + @Test func run_withoutName() { + // GIVEN: A run task without a name (backward compatibility) + let sut: ActionTask = .run { _ in } + + // THEN: Should have nil name + switch sut.operation { + case .run(_, let name, _, _, _, _): + #expect(name == nil) + default: + Issue.record("Expected run task") + } + } + + @Test func run_namePreservedAfterCatch() { + // GIVEN: A named run task with error handler + let sut: ActionTask = .run(name: "Load profile") { _ in } + .catch { _, _ in } + + // THEN: Should preserve the name + switch sut.operation { + case .run(_, let name, _, _, _, _): + #expect(name == "Load profile") + default: + Issue.record("Expected run task") + } + } + + @Test func run_namePreservedAfterCancellable() { + // GIVEN: A named run task made cancellable + let sut: ActionTask = .run(name: "Fetch data") { _ in } + .cancellable(id: "fetch") + + // THEN: Should preserve the name + switch sut.operation { + case .run(_, let name, _, _, _, _): + #expect(name == "Fetch data") + default: + Issue.record("Expected run task") + } + } + + @Test func run_namePreservedAfterPriority() { + // GIVEN: A named run task with priority + let sut: ActionTask = .run(name: "Critical operation") { _ in } + .priority(.high) + + // THEN: Should preserve the name + switch sut.operation { + case .run(_, let name, _, _, _, _): + #expect(name == "Critical operation") + default: + Issue.record("Expected run task") + } + } + + @Test func run_namePreservedThroughChaining() { + // GIVEN: A named run task with all configurations + let sut: ActionTask = .run(name: "🔄 Load user") { _ in } + .cancellable(id: "load", cancelInFlight: true) + .priority(.userInitiated) + .catch { _, _ in } + + // THEN: Should preserve the name through all chaining + switch sut.operation { + case .run(let id, let name, _, let onError, let cancelInFlight, let priority): + #expect(name == "🔄 Load user") + #expect(id == "load") + #expect(cancelInFlight == true) + #expect(priority == .userInitiated) + #expect(onError != nil) + default: + Issue.record("Expected run task") + } + } } From 6006c419ba284a0114929fdc595589d63274e729 Mon Sep 17 00:00:00 2001 From: Takeshi Shimada Date: Mon, 27 Oct 2025 22:17:52 +0900 Subject: [PATCH 2/2] style: Fix SwiftLint function_parameter_count violation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add swiftlint:disable:next comment for executeRunTask method which requires 6 parameters (id, name, operation, onError, cancelInFlight, priority) as part of the task naming feature implementation. This is an internal helper method where the parameter count is justified by the complexity it manages, including task identification, naming, operation execution, error handling, cancellation policy, and priority configuration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Sources/Flow/Store/Store.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Flow/Store/Store.swift b/Sources/Flow/Store/Store.swift index ced29be..31bdc62 100644 --- a/Sources/Flow/Store/Store.swift +++ b/Sources/Flow/Store/Store.swift @@ -204,6 +204,7 @@ public final class Store { /// /// This helper method handles the complexity of executing async operations, /// managing task cancellation, and error handling. + // swiftlint:disable:next function_parameter_count private func executeRunTask( id: String, name: String?,