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.