From 0bd5f63bfba077df6f1a6565bec40cd5d8f91d04 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:21:34 +0000 Subject: [PATCH 1/2] fix(macos): flush pending autosave before clearing state in closeDocument() (LUM-1272) - closeDocument() now calls save() before cancelling the autoSave task and clearing state, preventing silent data loss when the user closes within the 2-second debounce window. - save() captures title, wordCount, and documentClient as locals before the Task to prevent reading stale/cleared values from self after close. - save() uses [weak self] and a surfaceId scope guard so stale save responses from a previous document cannot clobber a newly-opened document's state. - createDocument() and closeDocument() reset isSaving/lastSaveError for clean state at document boundaries. - Remove redundant scheduleAutoSave() call in updateDocument() (became dead after PR #4834 moved the call before the coordinator guard). - Add deinit to cancel autoSaveTask per @Observable task lifecycle guidance. Apple refs checked (2026-04-28): - WWDC21 Protect mutable state with Swift actors - swift#79551 @Observable deinit actor isolation Co-Authored-By: ashlee@vellum.ai --- .../Features/MainWindow/DocumentManager.swift | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift b/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift index 8c636ccbb5d..02c2cb83fa2 100644 --- a/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift +++ b/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift @@ -10,6 +10,8 @@ private let log = Logger(subsystem: Bundle.appBundleIdentifier, category: "Docum @MainActor @Observable final class DocumentManager { + deinit { autoSaveTask?.cancel() } + var hasActiveDocument: Bool = false var title: String = "Untitled Document" var surfaceId: String? @@ -56,6 +58,8 @@ final class DocumentManager { self.initialContent = initialContent self.currentContent = initialContent self.wordCount = initialContent.split(whereSeparator: \.isWhitespace).count + self.isSaving = false + self.lastSaveError = nil self.hasActiveDocument = true // Initialize editor with content (or store as pending if coordinator not ready) @@ -97,8 +101,6 @@ final class DocumentManager { coordinator.sendContentUpdate(markdown: markdown, mode: mode) log.info("Document updated: mode=\(mode), length=\(markdown.count)") - - scheduleAutoSave() } /// Cancels any pending auto-save and schedules a new one 2 seconds from now. @@ -119,6 +121,7 @@ final class DocumentManager { } func closeDocument() { + save() autoSaveTask?.cancel() autoSaveTask = nil hasActiveDocument = false @@ -129,6 +132,8 @@ final class DocumentManager { wordCount = 0 initialContent = "" pendingInitialContent = nil + isSaving = false + lastSaveError = nil log.info("Document closed") } @@ -156,28 +161,29 @@ final class DocumentManager { func save() { guard let surfaceId = surfaceId, let conversationId = conversationId else { - log.warning("Cannot save: missing surfaceId or conversationId") - lastSaveError = "Cannot save: missing document information" return } + let titleToSave = title let contentToSave = currentContent ?? "" + let wordCountToSave = wordCount + let client = documentClient isSaving = true lastSaveError = nil - Task { - let response = await documentClient.saveDocument( + Task { [weak self] in + let response = await client.saveDocument( surfaceId: surfaceId, conversationId: conversationId, - title: title, + title: titleToSave, content: contentToSave, - wordCount: wordCount - ) - handleSaveResponse( - success: response?.success ?? false, - error: response?.error ?? (response == nil ? "Network error" : nil) + wordCount: wordCountToSave ) - log.info("Document save requested: \(surfaceId) - \(self.wordCount) words") + let success = response?.success ?? false + let error = response?.error ?? (response == nil ? "Network error" : nil) + log.info("Document save completed: \(surfaceId) - \(wordCountToSave) words - success: \(success)") + guard let self, self.surfaceId == surfaceId else { return } + handleSaveResponse(success: success, error: error) } } From 0b37619912edfd3b8fc1e584692f531d3ae81b76 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:27:49 +0000 Subject: [PATCH 2/2] fix: flush active document in createDocument() before overwriting state Callers like handleDocumentEditorShow and handleDocumentLoadResponse call createDocument() directly without closeDocument(). If a document has unsaved edits, the new document would silently overwrite them. Guard at the data layer so all callers get the flush automatically. Co-Authored-By: ashlee@vellum.ai --- .../vellum-assistant/Features/MainWindow/DocumentManager.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift b/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift index 02c2cb83fa2..82f8a8dd5c3 100644 --- a/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift +++ b/clients/macos/vellum-assistant/Features/MainWindow/DocumentManager.swift @@ -52,6 +52,9 @@ final class DocumentManager { } func createDocument(surfaceId: String, conversationId: String, title: String, initialContent: String) { + if hasActiveDocument { + closeDocument() + } self.surfaceId = surfaceId self.conversationId = conversationId self.title = title