Add unsaved-changes save prompt and crash-recovery draft

Replaces heuristic auto-save (goodbye-phrase detection, idle timeout,
min-message count, on-model-switch) with a standard macOS unsaved-changes
gate (Save/Don't Save/Cancel) on New Chat, Clear Chat, Load Conversation,
and Quit. The Save dialog gained a folder picker with inline "New Folder…"
creation.

Separately, the in-progress conversation is periodically mirrored to disk
(DraftRecoveryService, configurable interval in Settings, default 10s) and
offered back on next launch if oAI crashes or is force-quit, including the
model that was selected.

Two real bugs found via ObjectIdentifier/log-based diagnosis before this
worked correctly:
- oAIApp.init() wired AppDelegate.chatViewModel from its own @State read,
  which returned a throwaway ChatViewModel instance distinct from the one
  ContentView actually renders. Wiring moved to ContentView.onAppear.
- NSApplication.shared.delegate as? AppDelegate always failed silently:
  @NSApplicationDelegateAdaptor registers an internal SwiftUI.AppDelegate
  wrapper as the real NSApp.delegate (same name, different type in a
  different module), which forwards protocol methods but isn't castable
  to our type. AppDelegate now tracks itself via a static `shared`.

Also guards checkForCrashRecoveryDraft() against running under
XCTestConfigurationFilePath — oAITests is app-hosted, so xcodebuild test
launches this same app, and a leftover draft file on disk would otherwise
hang the entire test run on a blocking NSAlert with no one to click it.
This commit is contained in:
2026-07-31 08:11:12 +02:00
parent a306aaef9a
commit e3557a87df
12 changed files with 585 additions and 490 deletions
+4 -3
View File
@@ -15,7 +15,8 @@ A powerful native macOS AI chat application with support for multiple providers
### 💬 Core Chat Capabilities
- **Streaming Responses** - Real-time token streaming for faster interactions
- **Conversation Management** - Save, load, export, and search conversations
- **Conversation Management** - Save, load, export, and search conversations, organized into folders
- **Unsaved Changes & Crash Recovery** - Standard Mac-style save prompt on New Chat/Clear/Load/Quit with unsaved messages; the in-progress conversation (and selected model) is also mirrored to disk periodically and offered back on next launch if oAI crashes or is force-quit
- **Combine Conversations** - Merge 2+ saved conversations, either by chronological concatenation or AI-assisted synthesis
- **File Attachments** - Support for text files, images, and PDFs
- **Image Generation** - Create images with supported models (DALL-E, Flux, etc.) - renders inline in chat
@@ -169,12 +170,12 @@ Add your API keys in Settings (⌘,) → General tab:
- **Smart Context Selection** - Reduce token usage automatically
- **Semantic Search** - Enable AI-powered conversation search
- **Progressive Summarization** - Handle long conversations efficiently
- **Crash Recovery** - How often the in-progress conversation is mirrored to disk (Off/1s/10s/30s/60s), so a crash or force-quit doesn't lose it
#### Sync Tab
- **Repository URL** - Git repository for conversation backup
- **Authentication** - Username/password or access token
- **Auto-Save** - Configure automatic save triggers
- **Manual Sync** - One-click synchronization
- **Manual Sync** - One-click synchronization; every explicit save (⌘S, Save As, or the unsaved-changes prompt) also triggers a background sync automatically
#### Email Tab
- **Email Handler** - Configure automated email responses