From ea1ad1f2920d85226c3cd6f80a99fa3e94d30fe9 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 22 Jul 2026 14:21:09 -0400 Subject: [PATCH 1/8] feat: add gbkOptimizeMediaUploads feature flag Gates the upcoming native media upload processing for the experimental block editor, with a Debug-menu override for quick disabling. --- WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 90fac5e2cee0..5e146fd4aa9d 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -28,6 +28,7 @@ public enum FeatureFlag: Int, CaseIterable { case statsAds case customPostTypes case mediaLibraryV2 + case gbkMediaUploadOptimization /// Returns a boolean indicating if the feature is enabled. /// @@ -89,6 +90,8 @@ public enum FeatureFlag: Int, CaseIterable { return BuildConfiguration.current == .debug case .mediaLibraryV2: return BuildConfiguration.current == .debug + case .gbkMediaUploadOptimization: + return true } } @@ -133,6 +136,7 @@ extension FeatureFlag { case .statsAds: "Stats Ads Tab" case .customPostTypes: "Custom Post Types" case .mediaLibraryV2: "Media Library v2" + case .gbkMediaUploadOptimization: "Optimize Experimental Block Editor Uploads" } } } From 80fdab4d573465beea92e67275609ce253dce104 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 22 Jul 2026 14:21:21 -0400 Subject: [PATCH 2/8] build: use GutenbergKit pr-build/357 snapshot Points GutenbergKit at the XCFramework snapshot for wordpress-mobile/GutenbergKit#357, which adds the native media upload server and MediaUploadDelegate. Swap to a tagged release before merge. --- Modules/Package.resolved | 4 ++-- Modules/Package.swift | 2 +- Package.resolved | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 47ebf3c10ed2..0c7b1499acc7 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "7180587f49d3c3bfdb34cc3e80b2a9d22a3cd93e", - "version" : "0.18.1" + "branch" : "pr-build/357", + "revision" : "72d422699607aec7b5e666db422787b5e2a6378d" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 9d07d6b80bb7..8feb7ee92788 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -62,7 +62,7 @@ let package = Package( revision: "b34794c9a3f32312e1593d4a3d120572afa0d010" ), .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", from: "0.18.1"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/357"), .package( url: "https://github.com/automattic/wordpress-rs", exact: "0.6.0" diff --git a/Package.resolved b/Package.resolved index e4f561d43c78..76af08fe709c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "7180587f49d3c3bfdb34cc3e80b2a9d22a3cd93e", - "version" : "0.18.1" + "branch" : "pr-build/357", + "revision" : "72d422699607aec7b5e666db422787b5e2a6378d" } }, { From 2d6bdf13114e4e9c82cc5db549cbefed778921c2 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 22 Jul 2026 14:21:36 -0400 Subject: [PATCH 3/8] feat: process GutenbergKit media uploads with app Media settings Implements GutenbergKit's MediaUploadDelegate so device media picked in the experimental block editor is processed natively before upload, honoring the app's Media settings: Optimize Images, Max Image Upload Size, Image Quality, Max Video Upload Size, and Remove Location From Media. Previously these uploads went directly from the WebView to the REST API with no processing. GBKMediaUploadProcessor mirrors the exporter option mapping used by MediaImportService and reuses MediaURLExporter, so the editor now matches the behavior of the legacy editor and My Site > Media. GIFs and non-media files pass through untouched, and non-web-safe image formats (e.g. HEIC) are converted to JPEG. Uploads still use GutenbergKit's default uploader, which relays the raw WordPress response to the editor. --- .../GBKMediaUploadProcessor.swift | 129 ++++++++++++++++++ .../PostGBKEditorViewController.swift | 7 + 2 files changed, 136 insertions(+) create mode 100644 WordPress/Classes/ViewRelated/NewGutenberg/GBKMediaUploadProcessor.swift diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/GBKMediaUploadProcessor.swift b/WordPress/Classes/ViewRelated/NewGutenberg/GBKMediaUploadProcessor.swift new file mode 100644 index 000000000000..182fc633dfd5 --- /dev/null +++ b/WordPress/Classes/ViewRelated/NewGutenberg/GBKMediaUploadProcessor.swift @@ -0,0 +1,129 @@ +import Foundation +import GutenbergKit +import UniformTypeIdentifiers +import WordPressData + +/// Processes media files picked in the GutenbergKit editor before upload, +/// applying the app's Media settings (image optimization, max upload size, +/// image quality, video resolution, and location stripping). +/// +/// Assigned to `GutenbergKit.EditorViewController.mediaUploadDelegate`, which +/// holds it weakly and invokes it off the main actor, so the type is `Sendable` +/// and snapshots the `Blog`-derived values it needs at initialization. +final class GBKMediaUploadProcessor: MediaUploadDelegate, Sendable { + private let videoDurationLimit: TimeInterval? + private let allowableFileExtensions: Set + private let makeMediaSettings: @Sendable () -> MediaSettings + + /// Image types the WordPress REST API reliably accepts. Other image + /// formats (e.g. HEIC) are converted to JPEG during processing, mirroring + /// `ItemProviderMediaExporter`. + private static let webSafeImageTypes: Set = [.png, .jpeg, .gif, .svg] + + @MainActor + convenience init(blog: Blog) { + // HEIC isn't supported when uploading an image, so we filter it out, + // mirroring `MediaImportService`. + var allowedFileTypes = blog.allowedFileTypes + allowedFileTypes.remove("heic") + + self.init( + videoDurationLimit: blog.videoDurationLimit, + allowableFileExtensions: allowedFileTypes + ) + } + + init( + videoDurationLimit: TimeInterval?, + allowableFileExtensions: Set, + makeMediaSettings: @escaping @Sendable () -> MediaSettings = { MediaSettings() } + ) { + self.videoDurationLimit = videoDurationLimit + self.allowableFileExtensions = allowableFileExtensions + self.makeMediaSettings = makeMediaSettings + } + + // MARK: - MediaUploadDelegate + + func processFile(at url: URL, mimeType: String, filename: String) async throws -> ProcessedProxyFile { + let expected = try MediaURLExporter.expectedExport(with: url) + let settings = makeMediaSettings() + + switch expected { + case .gif: + // GIFs are uploaded unchanged; processing would only copy the file. + return .original + case .other: + // Non-media files are uploaded unchanged, but enforce the site's + // allowed file extensions, mirroring `MediaURLExporter.exportURL`. + if let fileExtension = url.typeIdentifierFileExtension, + !MediaImportService.defaultAllowableFileExtensions.contains(fileExtension), + !allowableFileExtensions.isEmpty, + !allowableFileExtensions.contains(fileExtension) + { + throw MediaURLExporter.URLExportError.unsupportedFileType + } + return .original + case .image: + // Skip processing when it would be a no-op: optimization and + // location stripping disabled, and the format is web-safe. + if !settings.imageOptimizationEnabled, + !settings.removeLocationSetting, + let type = url.typeIdentifier.flatMap(UTType.init), + Self.webSafeImageTypes.contains(type) + { + return .original + } + case .video: + // Always process video to apply the export preset, duration + // limit, and location stripping. + break + } + + let export = try await makeExporter(for: url, settings: settings).export() + + guard let mimeType = export.url.typeIdentifier.flatMap(UTType.init)?.preferredMIMEType else { + throw MediaURLExporter.URLExportError.unknownFileUTI + } + return .processed(export.url, mimeType: mimeType, filename: export.url.lastPathComponent) + } + + // MARK: - Exporter configuration + + /// Builds an exporter configured from the app's Media settings, mirroring + /// the option mapping in `MediaImportService`. + private func makeExporter(for url: URL, settings: MediaSettings) -> MediaURLExporter { + let exporter = MediaURLExporter(url: url) + // GutenbergKit deletes the processed file after uploading it, so the + // export is written to a temporary directory rather than the uploads + // directory tracked by `MediaFileManager`. + exporter.mediaDirectoryType = .temporary + + var imageOptions = MediaImageExporter.Options() + imageOptions.maximumImageSize = maximumImageSize(from: settings) + imageOptions.stripsGeoLocationIfNeeded = settings.removeLocationSetting + imageOptions.imageCompressionQuality = settings.imageQualityForUpload.doubleValue + if let type = url.typeIdentifier.flatMap(UTType.init), !Self.webSafeImageTypes.contains(type) { + imageOptions.exportImageType = UTType.jpeg.identifier + } + exporter.imageOptions = imageOptions + + var videoOptions = MediaVideoExporter.Options() + videoOptions.stripsGeoLocationIfNeeded = settings.removeLocationSetting + videoOptions.exportPreset = settings.maxVideoSizeSetting.videoPreset + videoOptions.durationLimit = videoDurationLimit + exporter.videoOptions = videoOptions + + var urlOptions = MediaURLExporter.Options() + urlOptions.allowableFileExtensions = allowableFileExtensions + urlOptions.stripsGeoLocationIfNeeded = settings.removeLocationSetting + exporter.urlOptions = urlOptions + + return exporter + } + + private func maximumImageSize(from settings: MediaSettings) -> CGFloat? { + let maxUploadSize = settings.imageSizeForUpload + return maxUploadSize < Int.max ? CGFloat(maxUploadSize) : nil + } +} diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift index 2fe894311e9a..2a99830baf58 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift @@ -13,6 +13,9 @@ class PostGBKEditorViewController: UIViewController, GutenbergKit.EditorViewCont /* private */ let editorViewController: GutenbergKit.EditorViewController private let status: String // TODO: Can be deleted? + /// Retains the media upload processor, which the editor holds weakly. + private let mediaUploadProcessor: GBKMediaUploadProcessor + private var keyboardShowObserver: Any? private var keyboardHideObserver: Any? private var keyboardFrame = CGRect.zero @@ -52,10 +55,14 @@ class PostGBKEditorViewController: UIViewController, GutenbergKit.EditorViewCont dependencies: cachedDependencies, mediaPicker: MediaPickerController(blog: blog) ) + self.mediaUploadProcessor = GBKMediaUploadProcessor(blog: blog) super.init(nibName: nil, bundle: nil) self.editorViewController.delegate = self + if FeatureFlag.gbkMediaUploadOptimization.enabled { + self.editorViewController.mediaUploadDelegate = mediaUploadProcessor + } } required init?(coder aDecoder: NSCoder) { From cf46c7069e56ccaa0713496bd4abfd83e3122bd0 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 22 Jul 2026 14:21:42 -0400 Subject: [PATCH 4/8] test: cover GBKMediaUploadProcessor media processing Verifies resizing, GPS stripping, HEIC-to-JPEG conversion, GIF and no-op passthrough, disallowed file extensions, and the video duration limit using the existing media fixtures. --- .../Media/GBKMediaUploadProcessorTests.swift | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 Tests/KeystoneTests/Tests/Features/Media/GBKMediaUploadProcessorTests.swift diff --git a/Tests/KeystoneTests/Tests/Features/Media/GBKMediaUploadProcessorTests.swift b/Tests/KeystoneTests/Tests/Features/Media/GBKMediaUploadProcessorTests.swift new file mode 100644 index 000000000000..9d6c5a052f49 --- /dev/null +++ b/Tests/KeystoneTests/Tests/Features/Media/GBKMediaUploadProcessorTests.swift @@ -0,0 +1,174 @@ +import Foundation +import ImageIO +import Testing +import UniformTypeIdentifiers +import WordPressShared + +@testable import WordPress + +struct GBKMediaUploadProcessorTests { + + // MARK: - Images + + @Test func imageIsResizedWhenOptimizationEnabled() async throws { + let settings = makeSettings() + settings.imageOptimizationEnabled = true + settings.maxImageSizeSetting = 200 + let processor = makeProcessor(settings: settings) + let url = try fixtureURL("test-image-device-photo-gps.jpg") + + let result = try await processor.processFile(at: url, mimeType: "image/jpeg", filename: url.lastPathComponent) + + guard case .processed(let outputURL, let mimeType, let filename) = result else { + Issue.record("Expected a processed file") + return + } + defer { cleanUp(outputURL) } + let size = try imageSize(at: outputURL) + #expect(max(size.width, size.height) == 200) + #expect(mimeType == "image/jpeg") + #expect(filename.hasPrefix("test-image-device-photo-gps")) + } + + @Test func imageIsUntouchedWhenProcessingWouldBeNoOp() async throws { + let settings = makeSettings() + settings.imageOptimizationEnabled = false + settings.removeLocationSetting = false + let processor = makeProcessor(settings: settings) + let url = try fixtureURL("test-image-device-photo-gps.jpg") + + let result = try await processor.processFile(at: url, mimeType: "image/jpeg", filename: url.lastPathComponent) + + guard case .original = result else { + Issue.record("Expected the original file to pass through") + return + } + } + + @Test func gpsDataIsStrippedWhenRemoveLocationEnabled() async throws { + let settings = makeSettings() + settings.imageOptimizationEnabled = false + settings.removeLocationSetting = true + let processor = makeProcessor(settings: settings) + let url = try fixtureURL("test-image-device-photo-gps.jpg") + + let result = try await processor.processFile(at: url, mimeType: "image/jpeg", filename: url.lastPathComponent) + + guard case .processed(let outputURL, _, _) = result else { + Issue.record("Expected a processed file") + return + } + defer { cleanUp(outputURL) } + #expect(try imageProperties(at: url)[kCGImagePropertyGPSDictionary] != nil) + #expect(try imageProperties(at: outputURL)[kCGImagePropertyGPSDictionary] == nil) + } + + @Test func heicIsConvertedToJPEG() async throws { + let settings = makeSettings() + settings.imageOptimizationEnabled = false + settings.removeLocationSetting = false + let processor = makeProcessor(settings: settings) + let url = try fixtureURL("iphone-photo.heic") + + let result = try await processor.processFile(at: url, mimeType: "image/heic", filename: url.lastPathComponent) + + guard case .processed(let outputURL, let mimeType, let filename) = result else { + Issue.record("Expected a processed file") + return + } + defer { cleanUp(outputURL) } + #expect(mimeType == "image/jpeg") + #expect(filename.hasSuffix(".jpg") || filename.hasSuffix(".jpeg")) + } + + // MARK: - GIFs and other files + + @Test func gifPassesThroughUntouched() async throws { + let processor = makeProcessor(settings: makeSettings()) + let url = try fixtureURL("test-gif.gif") + + let result = try await processor.processFile(at: url, mimeType: "image/gif", filename: url.lastPathComponent) + + guard case .original = result else { + Issue.record("Expected the original file to pass through") + return + } + } + + @Test func disallowedFileExtensionThrows() async throws { + let processor = GBKMediaUploadProcessor( + videoDurationLimit: nil, + allowableFileExtensions: ["pdf"], + makeMediaSettings: makeSettingsFactory(makeSettings()) + ) + let url = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt") + try "plain text".write(to: url, atomically: true, encoding: .utf8) + defer { cleanUp(url) } + + await #expect(throws: MediaURLExporter.URLExportError.self) { + try await processor.processFile(at: url, mimeType: "text/plain", filename: url.lastPathComponent) + } + } + + // MARK: - Videos + + @Test func videoExceedingDurationLimitThrows() async throws { + let processor = GBKMediaUploadProcessor( + videoDurationLimit: 1, + allowableFileExtensions: [], + makeMediaSettings: makeSettingsFactory(makeSettings()) + ) + let url = try fixtureURL("test-video-device-gps.m4v") + + await #expect(throws: (any Error).self) { + try await processor.processFile(at: url, mimeType: "video/mp4", filename: url.lastPathComponent) + } + } + + // MARK: - Helpers + + private func makeProcessor(settings: MediaSettings) -> GBKMediaUploadProcessor { + GBKMediaUploadProcessor( + videoDurationLimit: nil, + allowableFileExtensions: [], + makeMediaSettings: makeSettingsFactory(settings) + ) + } + + private func makeSettings() -> MediaSettings { + MediaSettings(database: EphemeralKeyValueDatabase()) + } + + private func makeSettingsFactory(_ settings: MediaSettings) -> @Sendable () -> MediaSettings { + nonisolated(unsafe) let settings = settings + return { settings } + } + + private func fixtureURL(_ filename: String) throws -> URL { + let bundle = Bundle(for: BundleAnchor.self) + let name = (filename as NSString).deletingPathExtension + let ext = (filename as NSString).pathExtension + let url = try #require(bundle.url(forResource: name, withExtension: ext)) + return url + } + + private func imageProperties(at url: URL) throws -> [CFString: Any] { + let source = try #require(CGImageSourceCreateWithURL(url as CFURL, nil)) + let properties = try #require(CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]) + return properties + } + + private func imageSize(at url: URL) throws -> CGSize { + let properties = try imageProperties(at: url) + let width = try #require(properties[kCGImagePropertyPixelWidth] as? CGFloat) + let height = try #require(properties[kCGImagePropertyPixelHeight] as? CGFloat) + return CGSize(width: width, height: height) + } + + private func cleanUp(_ url: URL) { + try? FileManager.default.removeItem(at: url) + } +} + +/// Anchor for resolving the test bundle from Swift Testing suites. +private final class BundleAnchor {} From f373395e0cc1bf63b1dc6885f6300cf337ee45ed Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 22 Jul 2026 15:21:13 -0400 Subject: [PATCH 5/8] feat: surface media upload optimization in Experimental Features Adds the gbkMediaUploadOptimization flag to the Experimental Features list and aligns display names with the "New Block Editor (NBE)" naming. --- WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift | 2 +- .../Classes/Utility/BuildInformation/RemoteFeatureFlag.swift | 2 +- .../Me/App Settings/ExperimentalFeaturesDataProvider.swift | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 5e146fd4aa9d..cc261bbfb987 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -136,7 +136,7 @@ extension FeatureFlag { case .statsAds: "Stats Ads Tab" case .customPostTypes: "Custom Post Types" case .mediaLibraryV2: "Media Library v2" - case .gbkMediaUploadOptimization: "Optimize Experimental Block Editor Uploads" + case .gbkMediaUploadOptimization: "NBE Media Upload Optimization" } } } diff --git a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift index 7618900e2fca..0c5a7bbe03bf 100644 --- a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift @@ -203,7 +203,7 @@ public enum RemoteFeatureFlag: Int, CaseIterable { case .dotComWebLogin: return "Log in to WordPress.com from web browser" case .newGutenberg: - return "Experimental Block Editor" + return "New Block Editor (NBE)" case .newGutenbergPlugins: return "Experimental Block Editor Plugins" case .statsAds: diff --git a/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift b/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift index 1cc02bc588e7..7008a91dccb4 100644 --- a/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift +++ b/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift @@ -9,6 +9,7 @@ class ExperimentalFeaturesDataProvider: ExperimentalFeaturesViewModel.DataProvid FeatureFlag.intelligence, FeatureFlag.newStats, RemoteFeatureFlag.newGutenberg, + FeatureFlag.gbkMediaUploadOptimization, FeatureFlag.customPostTypes, FeatureFlag.newSupport, ] @@ -77,6 +78,6 @@ class ExperimentalFeaturesDataProvider: ExperimentalFeaturesViewModel.DataProvid static let editorFeedbackMessage = NSLocalizedString("experimentalFeatures.editorFeedbackMessage", value: "Are you willing to share feedback on the experimental editor?", comment: "Message for the alert asking for feedback on the experimental editor") static let editorFeedbackDecline = NSLocalizedString("experimentalFeatures.editorFeedbackDecline", value: "Not now", comment: "Dismiss button title for the alert asking for feedback") static let editorFeedbackAccept = NSLocalizedString("experimentalFeatures.editorFeedbackAccept", value: "Send feedback", comment: "Accept button title for the alert asking for feedback") - static let editorNote = NSLocalizedString("experimentalFeatures.editorNote", value: "Experimental Block Editor will become the default in a future release and the ability to disable it will be removed.", comment: "Communicates the future removal of the option to disable the experimental editor, displayed beneath the experimental features list") + static let editorNote = NSLocalizedString("experimentalFeatures.editorNote.v2", value: "New Block Editor will become the default in a future release and the ability to disable it will be removed.", comment: "Communicates the future removal of the option to disable the experimental editor, displayed beneath the experimental features list") } } From 347f95b2548848d5dd12ae08bf254b46513217cc Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 23 Jul 2026 09:47:26 -0400 Subject: [PATCH 6/8] refactor: set media upload delegate unconditionally Removes the gbkMediaUploadOptimization gate ahead of reverting the flag. GutenbergKit degrades gracefully if the upload server cannot start, so a dedicated kill switch isn't needed. --- .../NewGutenberg/PostGBKEditorViewController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift index 2a99830baf58..9716bdae098f 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/PostGBKEditorViewController.swift @@ -60,9 +60,7 @@ class PostGBKEditorViewController: UIViewController, GutenbergKit.EditorViewCont super.init(nibName: nil, bundle: nil) self.editorViewController.delegate = self - if FeatureFlag.gbkMediaUploadOptimization.enabled { - self.editorViewController.mediaUploadDelegate = mediaUploadProcessor - } + self.editorViewController.mediaUploadDelegate = mediaUploadProcessor } required init?(coder aDecoder: NSCoder) { From be8615d279c2278d0ea7b97cc491210253165526 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 23 Jul 2026 09:47:29 -0400 Subject: [PATCH 7/8] Revert "feat: surface media upload optimization in Experimental Features" This reverts commit f373395e0cc1bf63b1dc6885f6300cf337ee45ed. --- WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift | 2 +- .../Classes/Utility/BuildInformation/RemoteFeatureFlag.swift | 2 +- .../Me/App Settings/ExperimentalFeaturesDataProvider.swift | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index cc261bbfb987..5e146fd4aa9d 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -136,7 +136,7 @@ extension FeatureFlag { case .statsAds: "Stats Ads Tab" case .customPostTypes: "Custom Post Types" case .mediaLibraryV2: "Media Library v2" - case .gbkMediaUploadOptimization: "NBE Media Upload Optimization" + case .gbkMediaUploadOptimization: "Optimize Experimental Block Editor Uploads" } } } diff --git a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift index 0c5a7bbe03bf..7618900e2fca 100644 --- a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift @@ -203,7 +203,7 @@ public enum RemoteFeatureFlag: Int, CaseIterable { case .dotComWebLogin: return "Log in to WordPress.com from web browser" case .newGutenberg: - return "New Block Editor (NBE)" + return "Experimental Block Editor" case .newGutenbergPlugins: return "Experimental Block Editor Plugins" case .statsAds: diff --git a/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift b/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift index 7008a91dccb4..1cc02bc588e7 100644 --- a/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift +++ b/WordPress/Classes/ViewRelated/Me/App Settings/ExperimentalFeaturesDataProvider.swift @@ -9,7 +9,6 @@ class ExperimentalFeaturesDataProvider: ExperimentalFeaturesViewModel.DataProvid FeatureFlag.intelligence, FeatureFlag.newStats, RemoteFeatureFlag.newGutenberg, - FeatureFlag.gbkMediaUploadOptimization, FeatureFlag.customPostTypes, FeatureFlag.newSupport, ] @@ -78,6 +77,6 @@ class ExperimentalFeaturesDataProvider: ExperimentalFeaturesViewModel.DataProvid static let editorFeedbackMessage = NSLocalizedString("experimentalFeatures.editorFeedbackMessage", value: "Are you willing to share feedback on the experimental editor?", comment: "Message for the alert asking for feedback on the experimental editor") static let editorFeedbackDecline = NSLocalizedString("experimentalFeatures.editorFeedbackDecline", value: "Not now", comment: "Dismiss button title for the alert asking for feedback") static let editorFeedbackAccept = NSLocalizedString("experimentalFeatures.editorFeedbackAccept", value: "Send feedback", comment: "Accept button title for the alert asking for feedback") - static let editorNote = NSLocalizedString("experimentalFeatures.editorNote.v2", value: "New Block Editor will become the default in a future release and the ability to disable it will be removed.", comment: "Communicates the future removal of the option to disable the experimental editor, displayed beneath the experimental features list") + static let editorNote = NSLocalizedString("experimentalFeatures.editorNote", value: "Experimental Block Editor will become the default in a future release and the ability to disable it will be removed.", comment: "Communicates the future removal of the option to disable the experimental editor, displayed beneath the experimental features list") } } From c908b31f9c3564639cd0e1935d57be90e2898ee2 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 23 Jul 2026 09:47:31 -0400 Subject: [PATCH 8/8] Revert "feat: add gbkOptimizeMediaUploads feature flag" This reverts commit ea1ad1f2920d85226c3cd6f80a99fa3e94d30fe9. --- WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 5e146fd4aa9d..90fac5e2cee0 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -28,7 +28,6 @@ public enum FeatureFlag: Int, CaseIterable { case statsAds case customPostTypes case mediaLibraryV2 - case gbkMediaUploadOptimization /// Returns a boolean indicating if the feature is enabled. /// @@ -90,8 +89,6 @@ public enum FeatureFlag: Int, CaseIterable { return BuildConfiguration.current == .debug case .mediaLibraryV2: return BuildConfiguration.current == .debug - case .gbkMediaUploadOptimization: - return true } } @@ -136,7 +133,6 @@ extension FeatureFlag { case .statsAds: "Stats Ads Tab" case .customPostTypes: "Custom Post Types" case .mediaLibraryV2: "Media Library v2" - case .gbkMediaUploadOptimization: "Optimize Experimental Block Editor Uploads" } } }