From cc36aa8f87b5665fd1aaea2880a2ccfe3a04e96f Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 21 Aug 2026 18:53:28 -0400 Subject: [PATCH] fix(ios): surface block inserter media failures to the user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `BlockInserterViewModel.error` was assigned on media import failure but never read, so a failed photo import or camera capture showed nothing at all — the inserter sheet stayed open with no feedback. Bind the property to an alert, and replace the system-derived messages with a small set of localized strings. `URLError.unknown`'s description tells a user nothing actionable, and it is the likeliest failure (an iCloud photo not downloaded to the device). The camera path's message was also an unlocalized string literal. Also log the underlying error on both paths so the detail dropped from the user-facing message stays diagnosable, and only surface the picker alert when an error actually occurred, so an empty selection does not raise a spurious one. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SrZVz6gAuLjo6Dj8hTNeJv --- .../GutenbergKit/Sources/EditorLocalization.swift | 8 ++++++++ .../Views/BlockInserter/BlockInserterView.swift | 7 +++++++ .../Views/BlockInserter/BlockInserterViewModel.swift | 11 +++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorLocalization.swift b/ios/Sources/GutenbergKit/Sources/EditorLocalization.swift index 22e2551a9..27a406724 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorLocalization.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorLocalization.swift @@ -11,6 +11,11 @@ public enum EditorLocalizableString { // MARK: - Media case failedToInsertMedia + case failedToLoadSelectedMedia + case failedToProcessCapturedMedia + + // MARK: - Common + case ok // MARK: - Patterns case patterns @@ -96,6 +101,9 @@ public final class EditorLocalization { case .search: "Search" case .insertBlock: "Insert Block" case .failedToInsertMedia: "Failed to insert media" + case .failedToLoadSelectedMedia: "The selected media could not be loaded. It may not be fully downloaded to this device." + case .failedToProcessCapturedMedia: "The captured media could not be processed." + case .ok: "OK" case .patterns: "Patterns" case .noPatternsFound: "No Patterns Found" case .insertPattern: "Insert Pattern" diff --git a/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterView.swift b/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterView.swift index a98609bbf..7d4554d36 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterView.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterView.swift @@ -73,6 +73,13 @@ struct BlockInserterView: View { } .ignoresSafeArea() } + .alert(item: $viewModel.error) { error in + Alert( + title: Text(EditorLocalization[.failedToInsertMedia]), + message: Text(error.message), + dismissButton: .default(Text(EditorLocalization[.ok])) + ) + } .animation(.smooth(duration: 2), value: viewModel.isProcessingMedia) .animation(.snappy, value: inlineSelectedMediaItems.count) .onDisappear { diff --git a/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterViewModel.swift b/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterViewModel.swift index 7c53876a2..80ce1ad99 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterViewModel.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/BlockInserter/BlockInserterViewModel.swift @@ -2,6 +2,7 @@ import SwiftUI import PhotosUI import Combine +import OSLog @MainActor class BlockInserterViewModel: ObservableObject { @@ -82,14 +83,15 @@ class BlockInserterViewModel: ObservableObject { } } catch { anyError = error + Logger.media.error("Failed to import picker selection: \(error)") } guard !Task.isCancelled else { return [] } - if results.isEmpty { - self.error = MediaError(message: anyError?.localizedDescription ?? EditorLocalization[.failedToInsertMedia]) + if results.isEmpty, anyError != nil { + self.error = MediaError(message: EditorLocalization[.failedToLoadSelectedMedia]) } return results @@ -109,7 +111,7 @@ class BlockInserterViewModel: ObservableObject { switch media { case .photo(let image): guard let imageData = image.jpegData(compressionQuality: 0.8) else { - throw MediaError(message: "Failed to convert image to JPEG") + throw MediaError(message: EditorLocalization[.failedToProcessCapturedMedia]) } let fileURL = try await fileManager.writeData(imageData, withExtension: "jpg") @@ -128,7 +130,8 @@ class BlockInserterViewModel: ObservableObject { return [mediaInfo] } catch { - self.error = MediaError(message: error.localizedDescription) + Logger.media.error("Failed to process captured media: \(error)") + self.error = MediaError(message: EditorLocalization[.failedToProcessCapturedMedia]) return [] } }