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 {