Changed the logging system so that error level logs are not automatically displayed in a modal to the user, instead they are all stored and only combined into one modal when a new `displayPendingErrors()` function is called.

PiperOrigin-RevId: 207947320
diff --git a/src/Tulsi/TulsiProjectDocument.swift b/src/Tulsi/TulsiProjectDocument.swift
index 3f408fc..a2ced92 100644
--- a/src/Tulsi/TulsiProjectDocument.swift
+++ b/src/Tulsi/TulsiProjectDocument.swift
@@ -129,6 +129,7 @@
 
   /// Array of user-facing messages, generally output by the Tulsi generator.
   @objc dynamic var messages = [UIMessage]()
+  var errors = [LogMessage]()
 
   lazy var bundleExtension: String = {
     TulsiProjectDocument.getTulsiBundleExtension()
@@ -148,6 +149,9 @@
                                                                                queue: OperationQueue.main) {
       [weak self] (notification: Notification) in
         guard let item = LogMessage(notification: notification) else {
+          if let showModal = notification.userInfo?["displayErrors"] as? Bool, showModal {
+            self?.displayErrorModal()
+          }
           return
         }
         self?.handleLogMessage(item)
@@ -478,6 +482,29 @@
 
   // MARK: - Private methods
 
+  // Idempotent function to gather all error messages that have been logged and create a single
+  // error modal to present to the user.
+  private func displayErrorModal() {
+    guard TulsiProjectDocument.showAlertsOnErrors else {
+      return
+    }
+
+    var errorMessages = [String]()
+    var details = [String]()
+
+    for error in errors {
+      errorMessages.append(error.message)
+      if let detail = error.details {
+        details.append(detail)
+      }
+    }
+    errors.removeAll()
+
+    if !errorMessages.isEmpty {
+      ErrorAlertView.displayModalError(errorMessages.joined(separator: "\n"), details: details.joined(separator: "\n"))
+    }
+  }
+
   private func handleLogMessage(_ item: LogMessage) {
     let fullMessage: String
     if let details = item.details {
@@ -489,9 +516,7 @@
     switch item.level {
       case .Error:
         messages.append(UIMessage(text: fullMessage, type: .error))
-        if TulsiProjectDocument.showAlertsOnErrors {
-          ErrorAlertView.displayModalError(item.message, details: item.details)
-        }
+        errors.append(item)
 
       case .Warning:
         messages.append(UIMessage(text: fullMessage, type: .warning))
@@ -567,7 +592,7 @@
         "and file a bug if appropriate."
     alert.alertStyle = .critical
 
-    if let details = details {
+    if let details = details, !details.isEmpty {
       alert.text = details
 
       var views: NSArray?
diff --git a/src/TulsiGenerator/TulsiNotifications.swift b/src/TulsiGenerator/TulsiNotifications.swift
index 9a6c968..4d98721 100644
--- a/src/TulsiGenerator/TulsiNotifications.swift
+++ b/src/TulsiGenerator/TulsiNotifications.swift
@@ -56,8 +56,17 @@
   public let details: String?
   public let context: String?
 
+  // Sends a notification to display any errors that have been logged with postError.
+  public static func displayPendingErrors() {
+    let userInfo = [ "displayErrors" : true ]
+    NotificationCenter.default.post(name: Notification.Name(rawValue: TulsiMessageNotification),
+                                    object: nil,
+                                    userInfo: userInfo)
+  }
+
   public static func postError(_ message: String, details: String? = nil, context: String? = nil) {
     postMessage(.Error, message: message, details: details, context: context)
+    displayPendingErrors()
   }
 
   public static func postWarning(_ message: String, details: String? = nil, context: String? = nil) {
diff --git a/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift b/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift
index 92e69a4..f95ca78 100644
--- a/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift
+++ b/src/TulsiGeneratorIntegrationTests/BazelIntegrationTestCase.swift
@@ -342,6 +342,10 @@
                                                         queue: nil) {
         [weak self] (notification: Notification) in
           guard let item = LogMessage(notification: notification) else {
+            guard !(notification.userInfo?["displayErrors"] as? Bool ?? false) else {
+              return
+            }
+
             XCTFail("Invalid message notification received (failed to convert to LogMessage)")
             return
           }