diff --git a/apps/ios/Tests/Logic/WatchChatStatusLocalizationTests.swift b/apps/ios/Tests/Logic/WatchChatStatusLocalizationTests.swift index d72fb61876f6..f82c6b5bf043 100644 --- a/apps/ios/Tests/Logic/WatchChatStatusLocalizationTests.swift +++ b/apps/ios/Tests/Logic/WatchChatStatusLocalizationTests.swift @@ -71,6 +71,18 @@ struct WatchChatStatusLocalizationTests { #expect(status.command == .sendChat) } + @Test func `legacy approval outcomes recover semantic localization`() throws { + let allowed = try #require( + WatchExecApprovalOutcome.decodeLegacyLocalizedText("Approval allowed once.")) + let custom = try #require( + WatchExecApprovalOutcome.decodeLegacyLocalizedText("Gateway custom outcome")) + + #expect(allowed.code == .allowedOnce) + #expect(allowed.verbatim == nil) + #expect(custom.code == .verbatim) + #expect(custom.verbatim == "Gateway custom outcome") + } + @Test func `gateway presentation localizes key and keeps backend override verbatim`() { let localized = OpenClawWatchAppStatus( code: .gatewayProblem, diff --git a/apps/ios/WatchApp/Sources/WatchInboxMessages.swift b/apps/ios/WatchApp/Sources/WatchInboxMessages.swift index 8fd3aaf755eb..3308dcc85266 100644 --- a/apps/ios/WatchApp/Sources/WatchInboxMessages.swift +++ b/apps/ios/WatchApp/Sources/WatchInboxMessages.swift @@ -377,7 +377,28 @@ struct WatchExecApprovalOutcome: Codable, Equatable { static func decodeLegacyLocalizedText(_ text: String) -> Self? { guard !text.isEmpty else { return nil } - return Self(code: .verbatim, verbatim: text) + return switch text { + case "Allowed once", "Approval allowed once.", "This approval was already allowed once.": + Self(code: .allowedOnce) + case "Approval set to Always Allow.", "This approval was already set to Always Allow.": + Self(code: .allowedAlways) + case "Denied", "Approval denied.", "This approval was already denied.": + Self(code: .denied) + case "Approval resolved": + Self(code: .resolved) + case "Approval expired": + Self(code: .expired) + case "Approval no longer available": + Self(code: .notFound) + case "Approval resolved elsewhere", "This approval was already resolved elsewhere.": + Self(code: .resolvedElsewhere) + case "Approval replaced": + Self(code: .replaced) + case "Approval unavailable": + Self(code: .unavailable) + default: + Self(code: .verbatim, verbatim: text) + } } } diff --git a/apps/ios/WatchApp/Sources/WatchInboxStore.swift b/apps/ios/WatchApp/Sources/WatchInboxStore.swift index c540f56c90de..545529846720 100644 --- a/apps/ios/WatchApp/Sources/WatchInboxStore.swift +++ b/apps/ios/WatchApp/Sources/WatchInboxStore.swift @@ -743,7 +743,8 @@ extension WatchInboxStore { .trimmingCharacters(in: .whitespacesAndNewlines) .prefix(160) let outcome = if let normalizedOutcomeText, !normalizedOutcomeText.isEmpty { - WatchExecApprovalOutcome(code: .verbatim, verbatim: String(normalizedOutcomeText)) + WatchExecApprovalOutcome.decodeLegacyLocalizedText(String(normalizedOutcomeText)) + ?? WatchExecApprovalOutcome(code: .unavailable) } else if message.source == "another-reviewer" { WatchExecApprovalOutcome(code: .resolvedElsewhere) } else {