Skip to content

Conversation

@twstokes
Copy link
Contributor

@twstokes twstokes commented Dec 3, 2025

Description

Instead of rendering one potentially massive Text view, lazy load it via array of strings. Logs on my device were large enough to crash the app due to the main thread watchdog.

lazy_logs.mp4

Testing instructions

Legacy support view

Ensure new support experience feature is disabled.

  1. Me -> Help & Support -> Logs -> Tap on an Activity Log
  • Expect the log to load immediately and you should be able to scroll + read the log in its entirety.
  1. Tap the Share icon while viewing a log
  • Expect the same options to be available as before and that sharing works (saving, copying, etc.).

New support view

Ensure new support experience feature is enabled.

  1. Me -> Application Logs -> Tap a log
  • Expect the log to load immediately and you should be able to scroll + read the log in its entirety.
  • Memory usage shouldn't inflate to gigabytes.
  1. Tap the Share icon while viewing a log
  • Expect the same options to be available as before and that sharing works (new support ticket, exporting as a file, etc.).

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 3, 2025

App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number30072
VersionPR #25046
Bundle IDcom.jetpack.alpha
Commita5a8e3d
Installation URL3ltjmldc9cpu0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 3, 2025

App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number30072
VersionPR #25046
Bundle IDorg.wordpress.alpha
Commita5a8e3d
Installation URL2f1s3gdirqra8
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@twstokes twstokes self-assigned this Dec 3, 2025
@twstokes twstokes requested review from dcalhoun and kean December 3, 2025 21:31
@twstokes twstokes marked this pull request as ready for review December 3, 2025 21:31
@twstokes twstokes added this to the 26.6 milestone Dec 3, 2025
@twstokes twstokes changed the title Lazy load log files to increase performance Lazy render logs to increase performance Dec 3, 2025
Copy link
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tested well for me. 🎉 Thanks!

I'll note these changes do not impact the experimental "New Support" feature. The logic for that resides in Modules/Sources/Support/UI/Application Logs/ActivityLogDetailView.swift. Claude suggested the following for applying the lazy render optimization there. I did not test the suggestion, nor do we necessarily need to address it in this PR, but I share it for additional context.

New Support lazy render optimization diff
diff --git a/Modules/Sources/Support/UI/Application Logs/ActivityLogDetailView.swift b/Modules/Sources/Support/UI/Application Logs/ActivityLogDetailView.swift
index 88f8f48ced..748c81f075 100644
--- a/Modules/Sources/Support/UI/Application Logs/ActivityLogDetailView.swift	
+++ b/Modules/Sources/Support/UI/Application Logs/ActivityLogDetailView.swift	
@@ -8,14 +8,14 @@ struct ActivityLogDetailView: View {
 
     enum ViewState: Equatable {
         case loading
-        case loaded(String, isSharing: Bool)
+        case loaded([String], isSharing: Bool)
         case error(Error)
 
         static func == (lhs: ActivityLogDetailView.ViewState, rhs: ActivityLogDetailView.ViewState) -> Bool {
             return switch (lhs, rhs) {
             case (.loading, .loading): true
-            case (.loaded(let lhscontent, let lhsisSharing), .loaded(let rhscontent, let rhsisSharing)):
-                lhscontent == rhscontent && lhsisSharing == rhsisSharing
+            case (.loaded(let lhsLines, let lhsisSharing), .loaded(let rhsLines, let rhsisSharing)):
+                lhsLines == rhsLines && lhsisSharing == rhsisSharing
             case (.error, .error): true
             default: false
             }
@@ -38,8 +38,8 @@ struct ActivityLogDetailView: View {
             switch self.state {
             case .loading:
                 self.loadingView
-            case .loaded(let content, _):
-                self.loadedView(content: content)
+            case .loaded(let lines, _):
+                self.loadedView(lines: lines)
             case .error(let error):
                 self.errorView(error: error)
             }
@@ -54,11 +54,11 @@ struct ActivityLogDetailView: View {
             }
         }
         .sheet(isPresented: self.$isDisplayingShareSheet, onDismiss: {
-            guard case .loaded(let content, _) = self.state else {
+            guard case .loaded(let lines, _) = self.state else {
                 return
             }
 
-            self.state = .loaded(content, isSharing: false)
+            self.state = .loaded(lines, isSharing: false)
         }, content: {
             ActivityLogSharingView(applicationLog: applicationLog) {
                 AnyView(erasing: Text("TODO: A new support request with the application log attached"))
@@ -87,15 +87,21 @@ struct ActivityLogDetailView: View {
     }
 
     @ViewBuilder
-    func loadedView(content: String) -> some View {
+    func loadedView(lines: [String]) -> some View {
         ScrollView {
-            VStack(alignment: .leading) {
-                TextEditor(text: .constant(content))
-                    .font(.system(.body, design: .monospaced))
-                    .fixedSize(horizontal: false, vertical: true)
-                    .scrollDisabled(true)
-                    .padding()
+            LazyVStack(alignment: .leading, spacing: 0) {
+                logLinesContent(lines: lines)
             }
+            .font(.system(.body, design: .monospaced))
+            .padding()
+        }
+    }
+
+    @ViewBuilder
+    private func logLinesContent(lines: [String]) -> some View {
+        ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
+            Text(line)
+                .frame(maxWidth: .infinity, alignment: .leading)
         }
     }
 
@@ -110,19 +116,22 @@ struct ActivityLogDetailView: View {
     private func loadLogContent() async {
         do {
             let content = try await self.dataProvider.readApplicationLog(applicationLog)
+            let lines = content
+                .split(separator: "\n", omittingEmptySubsequences: false)
+                .map(String.init)
 
-            self.state = .loaded(content, isSharing: false)
+            self.state = .loaded(lines, isSharing: false)
         } catch {
             self.state = .error(error)
         }
     }
 
     private func startSharing() {
-        guard case .loaded(let content, _) = self.state else {
+        guard case .loaded(let lines, _) = self.state else {
             return
         }
 
-        state = .loaded(content, isSharing: true)
+        state = .loaded(lines, isSharing: true)
     }
 }
 

@twstokes
Copy link
Contributor Author

twstokes commented Dec 5, 2025

This tested well for me. 🎉 Thanks!

I'll note these changes do not impact the experimental "New Support" feature. The logic for that resides in Modules/Sources/Support/UI/Application Logs/ActivityLogDetailView.swift. Claude suggested the following for applying the lazy render optimization there. I did not test the suggestion, nor do we necessarily need to address it in this PR, but I share it for additional context.

New Support lazy render optimization diff

Thanks @dcalhoun! I wasn't even aware of that view. I applied the same functionality to it and noticed a massive difference in memory usage when viewing a log:

Before After
Screenshot 2025-12-05 at 15 25 52 Screenshot 2025-12-05 at 15 30 13

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 5, 2025

@twstokes twstokes requested a review from dcalhoun December 5, 2025 20:37
Copy link
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested well for me—both the legacy and new support UI. 🚀

I applied the same functionality to it and noticed a massive difference in memory usage when viewing a log:

Whoa!

@twstokes twstokes merged commit 2107113 into trunk Dec 5, 2025
26 of 32 checks passed
@twstokes twstokes deleted the twstokes/lazy-logs branch December 5, 2025 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants