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:
@@ -511,9 +511,22 @@
|
||||
|
||||
<p>From the <strong>File menu</strong> you also have:</p>
|
||||
<ul>
|
||||
<li><strong>Save Chat (<kbd>⌘S</kbd>)</strong> — Re-saves under the current name, or prompts for a name if the conversation hasn't been saved yet.</li>
|
||||
<li><strong>Save Chat As…</strong> — Always prompts for a new name and creates a fresh copy, switching the session to that copy. Useful for branching a conversation.</li>
|
||||
<li><strong>Save Chat (<kbd>⌘S</kbd>)</strong> — Re-saves under the current name, or prompts for a name and folder if the conversation hasn't been saved yet.</li>
|
||||
<li><strong>Save Chat As…</strong> — Always prompts for a new name and folder and creates a fresh copy, switching the session to that copy. Useful for branching a conversation.</li>
|
||||
</ul>
|
||||
<p class="note">The Save dialog includes a folder picker — choose an existing folder or pick <strong>New Folder…</strong> to create one on the spot.</p>
|
||||
|
||||
<h3 id="unsaved-changes">Unsaved Changes & Crash Recovery</h3>
|
||||
<p>oAI tracks unsaved changes like a standard Mac document. Starting a New Chat, clearing the chat, loading a different conversation, or quitting the app while there are unsaved messages shows the standard save prompt:</p>
|
||||
<ul>
|
||||
<li><strong>Save</strong> — saves the conversation (prompting for a name and folder if it hasn't been saved yet), then proceeds</li>
|
||||
<li><strong>Don't Save</strong> (<kbd>⌘D</kbd> in the prompt) — discards the changes and proceeds</li>
|
||||
<li><strong>Cancel</strong> — stops the action so nothing is lost</li>
|
||||
</ul>
|
||||
|
||||
<div class="tip">
|
||||
<strong>💡 Crash Recovery:</strong> While you're chatting, oAI periodically mirrors the in-progress conversation to disk (every 10 seconds by default — adjustable or turned off in Settings → Advanced). If oAI crashes or is force-quit, the next launch offers to restore the conversation you were working on, including the model you had selected. This mirror is invisible and separate from your saved conversations — it's cleared automatically as soon as you save, discard, or restore it.
|
||||
</div>
|
||||
|
||||
<h3>Renaming Conversations</h3>
|
||||
<p>In the Conversations list (<kbd>⌘L</kbd>):</p>
|
||||
@@ -605,27 +618,10 @@
|
||||
<li>Click <strong>Clone Repository</strong> to initialize</li>
|
||||
</ol>
|
||||
|
||||
<h3>Auto-Save Features</h3>
|
||||
<p>When auto-save is enabled, oAI automatically saves and syncs conversations based on triggers:</p>
|
||||
|
||||
<h4>Auto-Save Triggers</h4>
|
||||
<ul>
|
||||
<li><strong>On App Start</strong> - Pulls and imports changes when oAI launches (no push)</li>
|
||||
<li><strong>On Model Switch</strong> - Saves when you change AI models</li>
|
||||
<li><strong>On App Quit</strong> - Saves before oAI closes</li>
|
||||
<li><strong>After Idle Timeout</strong> - Saves after 5 seconds of inactivity</li>
|
||||
</ul>
|
||||
|
||||
<h4>Auto-Save Settings</h4>
|
||||
<ul>
|
||||
<li><strong>Minimum Messages</strong> - Only save conversations with at least N messages (default: 5)</li>
|
||||
<li><strong>Auto-Export</strong> - Export conversations to markdown after save</li>
|
||||
<li><strong>Auto-Commit</strong> - Commit changes to git automatically</li>
|
||||
<li><strong>Auto-Push</strong> - Push commits to remote repository</li>
|
||||
</ul>
|
||||
<p>Saving to Git is explicit rather than automatic — see <a href="#unsaved-changes">Unsaved Changes & Crash Recovery</a> for how oAI tracks and prompts you to save. Every explicit save (<kbd>⌘S</kbd>, Save Chat As…, or the unsaved-changes prompt) triggers a background sync (export + commit + push) automatically when Git Sync is configured.</p>
|
||||
|
||||
<div class="warning">
|
||||
<strong>⚠️ Multi-Machine Warning:</strong> Running auto-sync on multiple machines simultaneously can cause merge conflicts. Use auto-sync on your primary machine only, or manually sync on others.
|
||||
<strong>⚠️ Multi-Machine Warning:</strong> Syncing on multiple machines simultaneously can cause merge conflicts. Pull before you start working on a different machine.
|
||||
</div>
|
||||
|
||||
<h3>Manual Sync Operations</h3>
|
||||
@@ -1544,11 +1540,17 @@ Whenever the user asks you to translate something, translate it to Norwegian Bok
|
||||
<dt><kbd>⌘M</kbd></dt>
|
||||
<dd>Model Selector</dd>
|
||||
|
||||
<dt><kbd>⌘N</kbd></dt>
|
||||
<dd>New Chat (prompts to save first if there are unsaved changes)</dd>
|
||||
|
||||
<dt><kbd>⌘K</kbd></dt>
|
||||
<dd>Clear Chat</dd>
|
||||
<dd>Clear Chat (prompts to save first if there are unsaved changes)</dd>
|
||||
|
||||
<dt><kbd>⌘O</kbd></dt>
|
||||
<dd>Open Chat…</dd>
|
||||
|
||||
<dt><kbd>⌘S</kbd></dt>
|
||||
<dd>Save Chat (re-saves if already named, prompts for name otherwise)</dd>
|
||||
<dd>Save Chat (re-saves if already named, prompts for name and folder otherwise)</dd>
|
||||
|
||||
<dt><kbd>⇧⌘S</kbd></dt>
|
||||
<dd>Show Statistics</dd>
|
||||
@@ -1609,13 +1611,7 @@ Whenever the user asks you to translate something, translate it to Norwegian Bok
|
||||
<li><strong>Authentication Method</strong> - Choose SSH, Password, or Access Token</li>
|
||||
<li><strong>Credentials</strong> - Enter username/password or access token (encrypted storage)</li>
|
||||
<li><strong>Local Path</strong> - Where to clone the repository locally (default: ~/oAI-Sync)</li>
|
||||
<li><strong>Auto-Save Settings</strong>:
|
||||
<ul>
|
||||
<li><strong>Enable Auto-Save</strong> - Automatically sync conversations</li>
|
||||
<li><strong>Minimum Messages</strong> - Only sync conversations with at least N messages</li>
|
||||
<li><strong>Triggers</strong> - Sync on app start, idle, goodbye phrases, model switch, or app quit</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Syncing happens automatically after every explicit save — no separate auto-save toggle needed. See <a href="#unsaved-changes">Unsaved Changes & Crash Recovery</a>.</li>
|
||||
<li><strong>Manual Sync</strong>:
|
||||
<ul>
|
||||
<li><strong>Initialize Repository</strong> - Clone repository for first-time setup</li>
|
||||
@@ -1702,6 +1698,7 @@ Whenever the user asks you to translate something, translate it to Norwegian Bok
|
||||
<li><strong>Smart Context Selection</strong> — Intelligently select relevant messages to reduce token usage</li>
|
||||
<li><strong>Semantic Search</strong> — Enable AI-powered conversation search using embeddings</li>
|
||||
<li><strong>Progressive Summarization</strong> — Automatically summarize old portions of long conversations</li>
|
||||
<li><strong>Crash Recovery</strong> — how often the in-progress conversation is mirrored to disk (Off, 1s, 10s, 30s, 60s); see <a href="#unsaved-changes">Unsaved Changes & Crash Recovery</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Backup Tab</h3>
|
||||
|
||||
Reference in New Issue
Block a user