From 0297c5fafdca3a169823c87e8c5cd2eafa5018b0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 03:44:45 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Offload=20synchronous=20blo?= =?UTF-8?q?cking=20operations=20in=20CacheoutViewModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Offloads `DiskInfo.current()`, `process.waitUntilExit()`, and `pipe.readDataToEndOfFile()` from the `@MainActor` to background tasks using `Task.detached` to prevent UI freezing. Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com> --- .jules/bolt.md | 3 +++ .../Cacheout/ViewModels/CacheoutViewModel.swift | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..078f60a --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-06-18 - Offload Synchronous Operations from Main Actor +**Learning:** In Swift Concurrency, `Task { ... }` created from within a `@MainActor` inherits that actor's context. To prevent synchronous, blocking operations (like `DiskInfo.current()`, `process.waitUntilExit()`, and `pipe.readDataToEndOfFile()`) from blocking the main thread and freezing the UI, they must be executed outside the actor's context. +**Action:** Use `Task.detached { ... }` which does not inherit the actor context, and `await` its `.value` property to safely return the result without blocking the `@MainActor`. diff --git a/Sources/Cacheout/ViewModels/CacheoutViewModel.swift b/Sources/Cacheout/ViewModels/CacheoutViewModel.swift index 13a9811..a389517 100644 --- a/Sources/Cacheout/ViewModels/CacheoutViewModel.swift +++ b/Sources/Cacheout/ViewModels/CacheoutViewModel.swift @@ -147,7 +147,8 @@ class CacheoutViewModel: ObservableObject { func scan() async { isScanning = true isNodeModulesScanning = true - diskInfo = DiskInfo.current() + // ⚡ Bolt: Offload blocking I/O to background thread to prevent UI freeze during scan startup + diskInfo = await Task.detached { DiskInfo.current() }.value // Scan caches and node_modules in parallel async let cacheResults = scanner.scanAll(CacheCategory.allCategories) @@ -241,10 +242,13 @@ class CacheoutViewModel: ObservableObject { ] do { - try process.run() - process.waitUntilExit() - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) ?? "" + // ⚡ Bolt: Offload synchronous process execution to prevent MainActor blocking (~3-5s UI freeze prevention) + let output = try await Task.detached { + try process.run() + process.waitUntilExit() + let data = pipe.fileHandleForReading.readDataToEndOfFile() + return String(data: data, encoding: .utf8) ?? "" + }.value if process.terminationStatus == 0 { // Extract "Total reclaimed space:" line @@ -270,7 +274,8 @@ class CacheoutViewModel: ObservableObject { } // Refresh disk info after prune - diskInfo = DiskInfo.current() + // ⚡ Bolt: Offload blocking I/O to background thread to prevent UI freeze + diskInfo = await Task.detached { DiskInfo.current() }.value } func clean() async {