Sync conversation notes via Git Sync, add discard shortcut to crash-recovery prompt

Notes files now export to notes/ + notes.json alongside conversations.json,
matching folders.json's manifest pattern: matched by conversation ID, never
overwrites notes a machine already has locally, same empty-state orphan-
cleanup safety guard as the existing conversation/folder sync code. Also
adds Cmd+D to the "Discard" button on the crash-recovery restore prompt.

Fixes two Swift 6 actor-isolation build warnings surfaced along the way:
ConversationNotesService and the String filename-sanitizing extension are
pure, state-free helpers called from nonisolated contexts (DatabaseService,
GitSyncService) but defaulted to @MainActor — marked nonisolated.
This commit is contained in:
2026-08-04 08:31:16 +02:00
parent 3414e37e24
commit d93c233453
7 changed files with 195 additions and 6 deletions
@@ -51,6 +51,29 @@ struct ConversationNotesServiceTests {
#expect(body == nil)
}
@Test("readRaw returns the exact on-disk content, ID header included")
func readRawIncludesHeader() {
let id = UUID()
let filename = "test-notes-\(UUID().uuidString).md"
defer { ConversationNotesService.shared.delete(filename: filename) }
ConversationNotesService.shared.write(body: "User prefers dark roast coffee.", filename: filename, conversationId: id)
let raw = ConversationNotesService.shared.readRaw(filename: filename)
#expect(raw == "**ID**: `\(id.uuidString)`\n\nUser prefers dark roast coffee.")
}
@Test("writeRaw then readRaw round-trips content byte-for-byte, with no header wrapping added")
func writeRawReadRawRoundTrip() {
let filename = "test-notes-\(UUID().uuidString).md"
defer { ConversationNotesService.shared.delete(filename: filename) }
let content = "**ID**: `12345678-1234-1234-1234-123456789012`\n\nImported from another machine."
ConversationNotesService.shared.writeRaw(content: content, filename: filename)
#expect(ConversationNotesService.shared.readRaw(filename: filename) == content)
}
@Test("stripIDHeader removes the embedded ID line and following blank line")
func stripIDHeaderRemovesHeader() {
let content = "**ID**: `12345678-1234-1234-1234-123456789012`\n\nActual notes content."