From b2282c11ea7bc07d1db4d4b1276431faec069188 Mon Sep 17 00:00:00 2001 From: kenny lopez Date: Fri, 23 Oct 2026 11:44:14 -0700 Subject: [PATCH] fix(mobile): keep saved Solo retries available outside discovery Allow locked retries to return their saved agent to Flutter for authoritative ownership revalidation. Keep inventory membership required for new chats and cover both native button and submit gates across inventory refreshes. Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez --- .../MeAgentAccessibilityTests.swift | 18 ++++++++++++++++-- .../ios/Runner/NativeProfileTextEditor.swift | 11 +++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/mobile/ios/NativeControlTests/MeAgentAccessibilityTests.swift b/mobile/ios/NativeControlTests/MeAgentAccessibilityTests.swift index a8fdd8440a..a21e4260a5 100644 --- a/mobile/ios/NativeControlTests/MeAgentAccessibilityTests.swift +++ b/mobile/ios/NativeControlTests/MeAgentAccessibilityTests.swift @@ -6,6 +6,7 @@ struct MeAgentAccessibilityTests { @MainActor static func main() { for locked in [false, true] { for empty in [false, true] { + var submitted: [String: String]? let controller = NativeProfileTextEditorViewController( title: "New chat", initialValue: "Chat", placeholder: "Chat name", multiline: false, pageBackgroundColor: .systemGroupedBackground, @@ -14,8 +15,8 @@ struct MeAgentAccessibilityTests { channelDescription: nil, originalName: nil, originalDescription: nil, canEditDetails: true, canEditCanvas: false, canvasContent: "", canvasLoaded: false, - chatOptions: ["presentationId": "test", "assistantsLoading": empty, "fieldsLocked": locked, "assistants": empty ? [] : [["id": "agent", "name": "Agent"]]], - onCancel: {}, onSet: { _ in } + chatOptions: ["selectedAssistant": "agent", "presentationId": "test", "assistantsLoading": empty, "fieldsLocked": locked, "assistants": empty ? [] : [["id": "agent", "name": "Agent"]]], + onCancel: {}, onSet: { submitted = $0 as? [String: String] } ) controller.loadViewIfNeeded() precondition(controller.numberOfSections(in: controller.tableView) == 3) @@ -31,6 +32,11 @@ struct MeAgentAccessibilityTests { precondition(cell.accessibilityTraits.contains(.notEnabled) == disabled) precondition(cell.accessibilityTraits.contains(.button) == !disabled) precondition((cell.selectionStyle == .none) == disabled) + let canSubmit = locked || !empty + precondition(controller.navigationItem.rightBarButtonItem?.isEnabled == canSubmit) + // Invoke the action even when disabled to check its independent gate. + _ = controller.perform(NSSelectorFromString("setTapped")) + precondition(submitted?["assistant"] == (canSubmit ? "agent" : nil)) if empty && !locked { controller.tableView.frame = CGRect(x: 0, y: 0, width: 390, height: 844) controller.tableView.reloadData() @@ -47,6 +53,14 @@ struct MeAgentAccessibilityTests { precondition(!updated.accessibilityTraits.contains(.notEnabled)) precondition(controller.tableView(controller.tableView, titleForFooterInSection: 2) == nil) } + submitted = nil + controller.updateChatAssistants([ + "presentationId": "test", + "assistants": empty ? [] : [["id": "agent", "name": "Agent"]], + ]) + precondition(controller.navigationItem.rightBarButtonItem?.isEnabled == canSubmit) + _ = controller.perform(NSSelectorFromString("setTapped")) + precondition(submitted?["assistant"] == (canSubmit ? "agent" : nil)) } } avatarTests() diff --git a/mobile/ios/Runner/NativeProfileTextEditor.swift b/mobile/ios/Runner/NativeProfileTextEditor.swift index 0b62fec004..317f64563b 100644 --- a/mobile/ios/Runner/NativeProfileTextEditor.swift +++ b/mobile/ios/Runner/NativeProfileTextEditor.swift @@ -449,13 +449,20 @@ private final class NativeProfileTextEditorViewController: updateNavigation() } + // A durable retry keeps its saved recipient even if discovery hides it. + // Flutter revalidates ownership before any creation writes. + private var canSubmitAssistant: Bool { + guard let selectedAssistant else { return false } + return fieldsLocked || assistants.contains { $0["id"] == selectedAssistant } + } + private func updateNavigation() { navigationItem.rightBarButtonItem?.isEnabled = canEditDetails && hasUnsavedChanges && (!isChannelEditor || !canonicalChannelName.isEmpty) && (!isChatEditor || ( !currentValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && currentValue.count <= 120 - && (isProjectEditor || assistants.contains { $0["id"] == selectedAssistant }) + && (isProjectEditor || canSubmitAssistant) )) navigationController?.isModalInPresentation = hasUnsavedChanges placeholderLabel.isHidden = !textView.text.isEmpty @@ -497,7 +504,7 @@ private final class NativeProfileTextEditorViewController: onSet(name) } else if isChatEditor { guard let selectedAssistant, - assistants.contains(where: { $0["id"] == selectedAssistant }), + canSubmitAssistant, !currentValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, currentValue.count <= 120 else { return } onSet(["name": currentValue, "assistant": selectedAssistant, "project": selectedProject ?? ""])